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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 01:40:05