You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java基于Bouncycastle实现FPE数字域加密报错如何修复

问题修复方案

错误根因

报错java.lang.IllegalArgumentException: input data outside of radix的核心原因是:
BouncyCastle的FPE算法实现要求传入的明文/密文字节数组,每个字节代表对应字符在目标字母表中的索引值,而非字符本身的编码值。你当前直接将明文字符串转成UTF-8字节,比如字符'0'的UTF-8编码值为48,远大于你设置的radix=10,自然触发值越界错误。
当你把radix设为64以上时,字符的ASCII值落在了radix范围内所以不会报错,但密文结果直接对应ASCII字符,自然会出现数字以外的字符,不符合格式保留要求。

修复步骤

  • 新增字母表映射逻辑:加密前将明文字符串的每个字符通过AlphabetMapper转换为索引字节数组,再传给加密接口
  • 加密得到的密文字节是索引值,需要通过AlphabetMapper转换回目标字母表的字符
  • 解密流程对应调整:先把密文字符串转成索引字节数组再解密,解密得到的索引字节数组再转回明文字符串

修复后完整代码

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.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 {
    // 定义仅包含数字的字母表
    private static final String ALPHABET = "0123456789";
    private static final AlphabetMapper MAPPER = new BasicAlphabetMapper(ALPHABET);
    private static final int RADIX = MAPPER.getRadix();

    @Test
    public void testAesFpe() throws Exception {
        SecretKey key = generateKey();
        byte[] tweak = getTweak();
        String plaintext = "510123456";
        log.info("明文: {}", plaintext);

        Cipher cipher = Cipher.getInstance("AES/FF3-1/NoPadding", new BouncyCastleProvider());
        String ciphertext = encrypt(cipher, key, tweak, plaintext);
        log.info("密文: {}", ciphertext);

        String decrypted = decrypt(cipher, key, tweak, ciphertext);
        log.info("解密结果: {}", decrypted);
        assertThat(decrypted, equalTo(plaintext));
    }

    public String encrypt(Cipher cipher, SecretKey key, byte[] tweak, String plaintext) throws Exception {
        // 字符串转索引字节数组
        byte[] plainIndexes = MAPPER.convertToIndexes(plaintext.toCharArray());
        AlgorithmParameterSpec fpeParameterSpec = new FPEParameterSpec(RADIX, tweak);
        cipher.init(Cipher.ENCRYPT_MODE, key, fpeParameterSpec);
        byte[] cipherIndexes = cipher.doFinal(plainIndexes);
        // 索引字节数组转回数字字符串
        return new String(MAPPER.convertToChars(cipherIndexes));
    }

    public String decrypt(Cipher cipher, SecretKey key, byte[] tweak, String ciphertext) throws Exception {
        // 密文字符串转索引字节数组
        byte[] cipherIndexes = MAPPER.convertToIndexes(ciphertext.toCharArray());
        AlgorithmParameterSpec fpeParameterSpec = new FPEParameterSpec(RADIX, tweak);
        cipher.init(Cipher.DECRYPT_MODE, key, fpeParameterSpec);
        byte[] plainIndexes = cipher.doFinal(cipherIndexes);
        // 索引字节数组转回明文字符串
        return new String(MAPPER.convertToChars(plainIndexes));
    }

    private SecretKey generateKey() throws NoSuchAlgorithmException {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        int keyLength = 256;
        keyGenerator.init(keyLength);
        return keyGenerator.generateKey();
    }

    private byte[] getTweak() {
        // FF3-1要求tweak长度为56bit即7字节,符合原设置
        int tweakLength = 7;
        byte[] tweak = new byte[tweakLength];
        new SecureRandom().nextBytes(tweak);
        return tweak;
    }
}

注意事项

  • FF3-1算法对明文长度有要求:radix=10时,明文长度需介于2到128位之间,示例中的9位长度符合要求
  • 加解密使用同一个AlphabetMapper实例即可,不需要重复创建

内容的提问来源于stack exchange,提问作者user4925383

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 03:09:02