使用 Bouncycastle 在 Java 中进行格式保留加密 (FPE)

Posted

技术标签:

【中文标题】使用 Bouncycastle 在 Java 中进行格式保留加密 (FPE)【英文标题】:Format Preserving Encryption (FPE) in Java with Bouncycastle 【发布时间】:2021-11-12 11:56:36 【问题描述】:

Release notes 用于 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] 范围之外的字符。如何解决?

【问题讨论】:

【参考方案1】:

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

样本 #11 来自 FF3samples.pdf 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 仍是草稿,没有发布测试examples。

FF3-1 的测试类SP80038GTest.java 似乎还没有完成。他们在 ff1Samples[] 中有 FF1 的所有 9 个 NIST 测试用例,但在 ff3_1Samples[] 中只有 1 个用于 FF3-1 的 256 位密钥。您可以考虑切换到 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")

【讨论】:

以上是关于使用 Bouncycastle 在 Java 中进行格式保留加密 (FPE)的主要内容,如果未能解决你的问题,请参考以下文章

Java 中带有 bouncycastle 的 PBKDF2

在没有 BouncyCastle 的情况下用 Java 创建 X509 证书?

使用 BouncyCastle 不推荐使用的方法进行贝宝按钮加密的 Java 代码 - 如何修复?

向证书请求添加属性,java + bouncycastle 1.48

使用Java中的bouncycastle生成p7b证书链

无法使用java中的pkcs#7和bouncyCastle签署zip文件