feat(outbound): SecretCipher - AES-GCM with JWT-derived key for at-rest secret encryption

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-19 16:13:57 +02:00
parent 46b8f63fd1
commit b8565af039
2 changed files with 104 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
package com.cameleer.server.app.outbound.crypto;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class SecretCipherTest {
private final SecretCipher cipher = new SecretCipher("test-jwt-secret-must-be-long-enough-to-derive");
@Test
void roundTrips() {
String plaintext = "my-hmac-secret-12345";
String ct = cipher.encrypt(plaintext);
assertThat(ct).isNotEqualTo(plaintext);
assertThat(cipher.decrypt(ct)).isEqualTo(plaintext);
}
@Test
void differentCiphertextsForSamePlaintext() {
String a = cipher.encrypt("x");
String b = cipher.encrypt("x");
assertThat(a).isNotEqualTo(b);
assertThat(cipher.decrypt(a)).isEqualTo(cipher.decrypt(b));
}
@Test
void decryptRejectsTamperedCiphertext() {
String ct = cipher.encrypt("abc");
String tampered = ct.substring(0, ct.length() - 2) + "00";
assertThatThrownBy(() -> cipher.decrypt(tampered))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
void decryptRejectsWrongKey() {
String ct = cipher.encrypt("abc");
SecretCipher other = new SecretCipher("some-other-jwt-secret-that-is-long-enough");
assertThatThrownBy(() -> other.decrypt(ct))
.isInstanceOf(IllegalArgumentException.class);
}
}