Skip to content

Commit

Permalink
Merge pull request #8 from statisticsnorway/string-encoding
Browse files Browse the repository at this point in the history
Support overriding charset
  • Loading branch information
kschulst authored Apr 11, 2023
2 parents 493ce30 + 7f1aa9d commit 6cc40bf
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 6 deletions.
8 changes: 4 additions & 4 deletions src/main/java/no/ssb/crypto/tink/fpe/FpeFf3.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public byte[] encrypt(final byte[] plaintext, final FpeParams params)
}

String tweak = hexTweakOf(params.getTweak());
String pt = b2s(plaintext);
String pt = b2s(plaintext, params.getCharset());

CharacterSkipper charSkipper = null;
if (params.getUnknownCharacterStrategy() == UnknownCharacterStrategy.SKIP) {
Expand Down Expand Up @@ -137,7 +137,7 @@ else if (params.getUnknownCharacterStrategy() == UnknownCharacterStrategy.FAIL)
charSkipper.injectSkippedInto(ciphertext);
}

return s2b(ciphertext.toString());
return s2b(ciphertext.toString(), params.getCharset());
}

/**
Expand All @@ -157,7 +157,7 @@ public byte[] decrypt(final byte[] ciphertext, final FpeParams params)
}

String tweak = hexTweakOf(params.getTweak());
String ct = b2s(ciphertext);
String ct = b2s(ciphertext, params.getCharset());
CharacterSkipper charSkipper = null;

if (params.getUnknownCharacterStrategy() == UnknownCharacterStrategy.SKIP) {
Expand All @@ -177,7 +177,7 @@ public byte[] decrypt(final byte[] ciphertext, final FpeParams params)
charSkipper.injectSkippedInto(plaintext);
}

return s2b(plaintext.toString());
return s2b(plaintext.toString(), params.getCharset());
}

// TODO: Unit test
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/no/ssb/crypto/tink/fpe/FpeParams.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import lombok.*;
import no.ssb.crypto.tink.fpe.util.ByteArrayUtil;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
* FpeParams is used as an argument when invoking encrypt and decrypt functions. It allows the user to specify
* additional details such as how to handle unknown characters, using a custom tweak, etc.
Expand Down Expand Up @@ -48,6 +51,13 @@ public static FpeParams with() {
*/
private Character redactionChar = null;

/**
* charset is the character set the underlying implementation expects when working with Strings.
*
* Defaults to UTF-8, but can be overridden if e.g. the plaintext is encoded differently.
*/
private Charset charset = StandardCharsets.UTF_8;

/**
* unknownCharacterStrategy defines the strategy for how the encryption/decryption process should handle characters
* that are not in the FPE alphabet.
Expand Down Expand Up @@ -78,4 +88,14 @@ public FpeParams redactionChar(char redactionChar) {
return this;
}

/**
* charset is the character set the underlying implementation expects when working with Strings.
*
* Defaults to UTF-8, but can be overridden if e.g. the plaintext is encoded differently.
*/
public FpeParams charset(Charset charset) {
this.charset = charset;
return this;
}

}
13 changes: 11 additions & 2 deletions src/main/java/no/ssb/crypto/tink/fpe/util/ByteArrayUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.experimental.UtilityClass;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

Expand Down Expand Up @@ -50,18 +51,26 @@ public static String byteArrayToIntString(byte[] byteArray) {
}

public static String b2s(byte[] bArr) {
return b2s(bArr, StandardCharsets.UTF_8);
}

public static String b2s(byte[] bArr, Charset charset) {
if (bArr == null || bArr.length == 0) {
return null;
}

return new String(bArr, StandardCharsets.UTF_8);
return new String(bArr, charset);
}

public static byte[] s2b(String s) {
return s2b(s, StandardCharsets.UTF_8);
}

public static byte[] s2b(String s, Charset charset) {
if (s == null || s.length() == 0) {
return null;
}
return s.getBytes(StandardCharsets.UTF_8);
return s.getBytes(charset);
}

}
30 changes: 30 additions & 0 deletions src/test/java/no/ssb/crypto/tink/fpe/FpeFf3Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import java.nio.charset.StandardCharsets;

import static no.ssb.crypto.tink.fpe.FpeFfxKeyType.FPE_FF31_256_ALPHANUMERIC;
import static no.ssb.crypto.tink.fpe.UnknownCharacterStrategy.*;
import static no.ssb.crypto.tink.fpe.util.ByteArrayUtil.b2s;
Expand Down Expand Up @@ -162,4 +164,32 @@ void createKey() throws Exception {
System.out.println(keyset);
}

@Test
void ff31_encrypt_decrypt_with_different_string_encoding() throws Exception {
KeysetHandle keysetHandle = TinkUtil.readKeyset(KEYSET_JSON_FF31_256_ALPHANUMERIC);
Fpe fpe = keysetHandle.getPrimitive(Fpe.class);
FpeParams paramsUtf8 = FpeParams.with().unknownCharacterStrategy(SKIP);
FpeParams paramsLatin1 = FpeParams.with().unknownCharacterStrategy(SKIP).charset(StandardCharsets.ISO_8859_1);
String plaintextStr = "Lörem ïpsum dôlor sit ämêt."; // funny characters

// utf-8
byte[] utf8Plaintext = plaintextStr.getBytes(StandardCharsets.UTF_8);
byte[] utf8Ciphertext = fpe.encrypt(utf8Plaintext, paramsUtf8);
byte[] utf8PlaintextRestored = fpe.decrypt(utf8Ciphertext, paramsUtf8);
assertThat(utf8PlaintextRestored).isEqualTo(utf8Plaintext);

// latin-1 (ISO-8859-1)
byte[] latin1Plaintext = plaintextStr.getBytes(StandardCharsets.ISO_8859_1);
byte[] latin1Ciphertext = fpe.encrypt(latin1Plaintext, paramsLatin1);
byte[] latin1PlaintextRestored = fpe.decrypt(latin1Ciphertext, paramsLatin1);
assertThat(latin1PlaintextRestored).isEqualTo(latin1Plaintext);

// Ciphertexts will be different if using different encodings
assertThat(utf8Ciphertext).isNotEqualTo(latin1Ciphertext);

// Ensure the original and restored plaintexts match, regardless of the encoding used.
assertThat(plaintextStr).isEqualTo(new String(utf8PlaintextRestored, StandardCharsets.UTF_8));
assertThat(plaintextStr).isEqualTo(new String(latin1PlaintextRestored, StandardCharsets.ISO_8859_1));
}

}

0 comments on commit 6cc40bf

Please sign in to comment.