Java:采用Base64编码的AES / CFB / NoPadding加密

我想使用AES / CFB / NoPadding在Java中加密字节。

我在Stackoverflow上发现了以下问题,但它仅涉及解密功能: Golang中的AES加密和Java中的解密

我将如何在Java中编写与以下Go代码类似的加密功能?

package main

import (
    "io"
    "crypto/aes"
    "crypto/cipher"
    "encoding/base64"
    "crypto/rand"
)

func encrypt(key, data []byte) string {
    block, err := aes.NewCipher(key)
    if err != nil {
      return nil, err
    }
    encoded := base64.StdEncoding.EncodeToString(data)
    ciphertext := make( []byte, aes.BlockSize+len(encoded) )
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
      return nil, err
    }
    cfb := cipher.NewCFBEncrypter(block, iv)
    cfb.XORKeyStream( ciphertext[aes.BlockSize:], []byte(encoded) )
    return ciphertext, nil
}

我的Golang解密函数如下所示(它应该返回base64代码):

func decrypt(key, data []byte) ([]byte, error) {
  blockcipher, err := aes.NewCipher(key)
  if err != nil {
    return nil, err
  }
  if len(data) < aes.BlockSize {
    return nil, errors.New("ciphertext too short")
  }
  iv := data[:aes.BlockSize]
  data = data[aes.BlockSize:]
  cfb := cipher.NewCFBDecrypter(blockcipher, iv)
  cfb.XORKeyStream(data, data)
  return data, nil
}

我当前的Java加密代码(似乎无法解密)如下所示:

private byte[] encrypt(byte[] payload) {
    try {
        SecretKeySpec key_spec = new SecretKeySpec(current_encryption_key, "AES");
        Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
        byte[] encoded_payload = Base64.encode(payload, Base64.DEFAULT);
        IvParameterSpec iv = new IvParameterSpec( new byte[16] );
        cipher.init(Cipher.ENCRYPT_MODE, key_spec, iv);
        return cipher.doFinal(encoded_payload);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return new byte[0];
}

我的加密代码如下所示(并且在Golang和Java上都可以正常工作):

private byte[] decrypt(byte[] payload) {
    try {
        SecretKeySpec key_spec = new SecretKeySpec(current_encryption_key, "AES");
        Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
        int block_size = cipher.getBlockSize();
        IvParameterSpec iv = new IvParameterSpec( Arrays.copyOf(payload, block_size) );
        byte[] decryption_data = Arrays.copyOfRange(payload, block_size, payload.length);
        cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
        byte[] decrypted_payload = cipher.doFinal(decryption_data);
        return Base64.decode(decrypted_payload, Base64.DEFAULT);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return new byte[0];
}

当我用Java加密某些内容,然后尝试在Java中使用解密器时,出现了以下解密错误:

04-13 14:16:48.382 3791-3791/com.domain.interpretest W/System.err: java.lang.IllegalArgumentException: 16 > 9
04-13 14:16:48.388 3791-3791/com.domain.interpretest W/System.err:     at java.util.Arrays.copyOfRange(Arrays.java:3447)

Summary of your Go encryption:

  • encrypt the base64-ed data with a random IV (and key you don't describe), putting the IV followed by the ciphertext in a single buffer

Summary of your Java decryption:

  • take the first block from the buffer and use it as IV to decrypt the remainder of the buffer, and de-base64 it

These match.

Summary of your Java encryption:

  • encrypt with a fixed (all-zero) IV, and return a buffer containing only the ciphertext, not the IV anyplace

This does not match. The decryption tries to remove the IV from a buffer that doesn't have the IV in it. In fact your ciphertext is only 9 bytes (although I don't understand why it's not a multiple of 4); this is shorter than one AES block, so the Arrays.copyOfRange fails outright.

Solution: your Java encryption should use a random IV and return a buffer containing the IV followed by the ciphertext, like your Go encryption does. One approach that fairly closely mimics your Go:

// once, during initialization 
SecureRandom rand = new SecureRandom(); // or .getInstance* as you prefer 

// unchanged 
SecretKeySpec key_spec = new SecretKeySpec(current_encryption_key, "AES");
Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
byte[] encoded_payload = Base64.encode(payload, Base64.DEFAULT);
// changed 
int block_size = cipher.getBlockSize();
// create random IV
byte[] buffer = new byte[block_size];
rand.nextBytes(buffer);
IvParameterSpec iv = new IvParameterSpec (buffer);
// expand buffer already containing IV to make room for ciphertext
buffer = Arrays.copyOf (buffer, block_size+encoded_payload.length);
// unchanged 
cipher.init(Cipher.ENCRYPT_MODE, key_spec, iv);
// changed
// do encryption into correct part of existing buffer
cipher.doFinal(encoded_payload,0,encoded_payload.length, buffer,block_size);
return buffer;

PS: why are you bothering to base64 encode, and decode, your plaintext? AES, like all modern ciphers, can handle any combination of bits just fine. It is more common to base64 the ciphertext and IV when other code can't handle them as bits/binary.