Java/Scala中用Bouncy Castle FPE加密遇input too short错误
格式保留加密(FPE)处理短输入时报错问题
我在Java/Scala中使用Bouncy Castle实现格式保留加密(FPE)时,遇到输入数据长度为3个字符或更短的情况,会抛出如下异常:
Exception in thread "main" java.lang.IllegalArgumentException: input too short at org.bouncycastle.crypto.fpe.SP80038G.checkLength(Unknown Source) at org.bouncycastle.crypto.fpe.SP80038G.checkData(Unknown Source) at org.bouncycastle.crypto.fpe.SP80038G.checkArgs(Unknown Source) at org.bouncycastle.crypto.fpe.SP80038G.encryptFF1(Unknown Source) at org.bouncycastle.crypto.fpe.FPEFF1Engine.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 java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202) at org.example.AESFPE2.encrypt(AESFPE2.scala:38) at org.example.AESFPE2.encrypt_test(AESFPE2.scala:25) at org.example.App$.main(Main.scala:18) at org.example.App.main(Main.scala)
我的测试代码如下(其余逻辑参考相关FPE实现方案):
class AESFPE2 { @throws[Exception] def encrypt_test(): Unit = { val key = getKeyFromPassword("foo", "bar") val tweak = getTweak val alphabetMapper = new BasicAlphabetMapper("\"'^*[]{}<>ºª|+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzáéíóúü!¿?¡=()/\\ _;:,.ç") val radix = alphabetMapper.getRadix val plaintext = "abc".toCharArray val plain_bytes = alphabetMapper.convertToIndexes(plaintext) val cipher = Cipher.getInstance("AES/FF1/NoPadding", new BouncyCastleProvider()) val cipher_bytes = encrypt(cipher, key, tweak, radix, plain_bytes) val decrypted = decrypt(cipher, key, tweak, radix, cipher_bytes) val cipher_chars = alphabetMapper.convertToChars(cipher_bytes) println(new String(cipher_chars)) val plain_chars = alphabetMapper.convertToChars(decrypted) println(new String(plain_chars)) } @throws[NoSuchAlgorithmException] @throws[InvalidKeySpecException] def getKeyFromPassword(password: String, salt: String): SecretKey = { val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") val spec = new PBEKeySpec(password.toCharArray, salt.getBytes, 65536, 256) val secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded, "AES") secret } }
问题原因
这个报错是因为你使用的FF1模式FPE算法遵循NIST SP 800-38G标准,对输入长度有硬性要求:输入字符数n必须满足 n ≥ ⌈log₂(radix)⌉(即基数的二进制对数向上取整)。
从你的代码看,自定义字符集的基数radix超过100,计算得log₂(100)≈6.64,所以FF1要求输入长度至少为7,你输入3个字符自然不满足要求,触发长度检查异常。
解决方法
1. 对短输入进行可逆填充
如果业务必须处理短输入,可以通过可逆填充将输入扩展到满足算法要求的长度,加密后再截取原长度作为最终密文;解密时反向操作即可。
示例代码修改:
def encrypt_test(): Unit = { val key = getKeyFromPassword("foo", "bar") val tweak = getTweak val alphabetMapper = new BasicAlphabetMapper("\"'^*[]{}<>ºª|+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzáéíóúü!¿?¡=()/\\ _;:,.ç") val radix = alphabetMapper.getRadix val plaintext = "abc".toCharArray val originalLength = plaintext.length // 计算FF1要求的最小输入长度 val minLength = Math.ceil(Math.log(radix) / Math.log(2)).toInt.max(2) // 对短输入进行填充(用最后一个字符重复,确保可逆) val processedPlaintext = if (originalLength < minLength) { plaintext ++ Array.fill(minLength - originalLength)(plaintext.last) } else { plaintext } val plain_bytes = alphabetMapper.convertToIndexes(processedPlaintext) val cipher = Cipher.getInstance("AES/FF1/NoPadding", new BouncyCastleProvider()) val cipher_bytes = encrypt(cipher, key, tweak, radix, plain_bytes) // 截取原长度的密文结果 val finalCipherBytes = cipher_bytes.take(originalLength) val cipher_chars = alphabetMapper.convertToChars(finalCipherBytes) println(new String(cipher_chars)) // 解密时反向操作:填充密文到最小长度 val processedCipherBytes = if (originalLength < minLength) { finalCipherBytes ++ Array.fill(minLength - originalLength)(finalCipherBytes.last) } else { finalCipherBytes } val decrypted = decrypt(cipher, key, tweak, radix, processedCipherBytes) val plain_chars = alphabetMapper.convertToChars(decrypted.take(originalLength)) println(new String(plain_chars)) }
2. 改用FF3模式(需评估安全性)
FF3模式的输入长度要求相对宽松:n ≥ 2且radix^n ≥ 1,000,000。对于你的大基数字符集(radix>100),100^3=1,000,000刚好满足要求,3个字符的输入可以正常处理。
修改Cipher实例化代码即可:
val cipher = Cipher.getInstance("AES/FF3/NoPadding", new BouncyCastleProvider())
注意:FF3在某些场景下的安全性弱于FF1,若对加密安全性要求较高,优先选择填充方案。
3. 调整字符集缩小基数
如果业务允许,可以精简字符集缩小radix,从而降低最小输入长度要求。比如若radix=16,log₂(16)=4,则最小输入长度为4,更易满足短输入场景。
内容的提问来源于stack exchange,提问作者Gustach
相关产品推荐
相关产品推荐

