Java与Bouncycastle中的格式保留加密(FPE)



Bouncycastle发布说明:1.69(2021年6月7日)状态:

SP 800-38G中两种FPE算法FF1和FF3-1的实现已添加到轻量级API和JCE提供程序中。

这些可以在bcprov-jdk15onJAR中找到。

下面是尝试使用它的代码:

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.crypto.AlphabetMapper;
import org.bouncycastle.crypto.util.BasicAlphabetMapper;
import org.bouncycastle.jcajce.spec.FPEParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.testng.annotations.Test;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
@Slf4j
public class AesFpe {
@Test
public void testAesFpe() throws Exception {
SecretKey key = generateKey();
byte[] tweak = getTweak();
int radix = getRadix("0123456789");
Charset encoding = StandardCharsets.UTF_8;
byte[] plaintext = "510123456".getBytes(encoding);
Cipher cipher = Cipher.getInstance("AES/FF3-1/NoPadding", new BouncyCastleProvider());
byte[] ciphertext = encrypt(cipher, key, tweak, radix, plaintext);
log.info("Ciphertext: {}", new String(ciphertext));
byte[] decrypted = decrypt(cipher, key, tweak, radix, ciphertext);
assertThat(decrypted, equalTo(plaintext));
}
public byte[] encrypt(Cipher cipher, SecretKey key, byte[] tweak, int radix, byte[] plaintext) throws Exception {
AlgorithmParameterSpec fpeParameterSpec = new FPEParameterSpec(radix, tweak);
cipher.init(Cipher.ENCRYPT_MODE, key, fpeParameterSpec);
return cipher.doFinal(plaintext);
}
public byte[] decrypt(Cipher cipher, SecretKey key, byte[] tweak, int radix, byte[] ciphertext) throws Exception {
AlgorithmParameterSpec fpeParameterSpec = new FPEParameterSpec(radix, tweak);
cipher.init(Cipher.DECRYPT_MODE, key, fpeParameterSpec);
return cipher.doFinal(ciphertext);
}
private SecretKey generateKey() throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
int keyLength = 256;
keyGenerator.init(keyLength);
return keyGenerator.generateKey();
}
private byte[] getTweak() {
int tweakLength = 7;
byte[] tweak = new byte[tweakLength];
new SecureRandom().nextBytes(tweak);
return tweak;
}
private int getRadix(String alphabet) {
AlphabetMapper alphabetMapper = new BasicAlphabetMapper(alphabet);
int radix = alphabetMapper.getRadix();
log.info("Radix: {}", radix);
return radix;
}
}

我还没有遇到一个如何正确使用它的例子。问题似乎出在基数上。执行上述结果,并使用以下堆栈跟踪:

java.lang.IllegalArgumentException: input data outside of radix
at org.bouncycastle.crypto.fpe.SP80038G.checkData(Unknown Source)
at org.bouncycastle.crypto.fpe.SP80038G.checkArgs(Unknown Source)
at org.bouncycastle.crypto.fpe.SP80038G.encryptFF3_1(Unknown Source)
at org.bouncycastle.crypto.fpe.FPEFF3_1Engine.encryptBlock(Unknown Source)
at org.bouncycastle.crypto.fpe.FPEEngine.processBlock(Unknown Source)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$BufferedFPEBlockCipher.doFinal(Unknown Source)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(Unknown Source)
at javax.crypto.Cipher.doFinal(Cipher.java:2164)

基数设置为64或更高时,此代码可以工作,但这不再是FPE -密文包含[0-9]范围之外的字符。如何解决这个问题?

BouncyCastle可能不支持原始的FF3,但NIST已经发布了使用基数10和256位密钥进行8字节调整的示例:

样本#11
FF3-AES256
密钥为EF 43 59 D8 D5 80 AA 4F 7F 03 6D 6F 04 FC 6A 94 2B 7E 15 16 28
AE D2 A6 AB F7 15 88 09 CF 4F 3C
基数= 10
调整为D8 E7 92 0A FA 33 0A 73
明文为890121234567890000
密文为922011205562777495

FF3-1仍然是一个草案,没有公布的测试示例。

测试类SP80038GTest.java似乎未完成FF3-1。他们在ff1Samples[]中有FF1的所有九个NIST测试用例,但在ff3_1Samples[]中只有一个256位密钥的FF3-1测试用例。你可以考虑切换到BouncyCastles的FF1以获得适当的测试覆盖率,或者使用我的Mysto FF3;FF3-1实现。

以下是BouncyCastle FF3-1中的一个测试示例,基数为10,7字节和256位密钥:

private FFSample(int radix, byte[] key, byte[] plaintext, byte[] ciphertext, byte[] tweak)
{
...
} 
FFSample.from(10, "1A58964B681384806A5A7639915ED0BE837C9C50C150AFD8F73445C0438CACF3", "4752683571", "2234571788", "CE3EBD69454984")

你可以试试我的代码。我可以成功运行这个测试用例

@Test
public void testAesFpe() throws Exception {
SecretKey key = generateKey();
byte[] tweak = getTweak();
AlphabetMapper alphabetMapper = new BasicAlphabetMapper("0123456789");
int radix = alphabetMapper.getRadix();
char[] plaintext = "510123456".toCharArray();
byte[] plain_bytes = alphabetMapper.convertToIndexes(plaintext);
Cipher cipher = Cipher.getInstance("AES/FF1/NoPadding", new BouncyCastleProvider());
byte[] cipher_bytes = encrypt(cipher, key, tweak, radix, plain_bytes);
byte[] decrypted = decrypt(cipher, key, tweak, radix, cipher_bytes);
char[] cipher_chars = alphabetMapper.convertToChars(cipher_bytes);
System.out.println(new String(cipher_chars));
char[] plain_chars = alphabetMapper.convertToChars(decrypted);
System.out.println(new String(plain_chars));
}

最新更新