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
相关产品推荐
相关产品推荐

