在《golang实现RSA和AES加解密》一文有提到AES CFB加密方法,而里面有介绍golang官方使用的一段代码,而且在一个项目中就是使用了示例中的代码,所不同的,只不过是将其中的hex.DecodeString对应的key值做了更改。不过后来因为某些原因,想要使用其他语言实现下,发现使用起来还是有点蛋疼的。这里记录下其中的细节。

一、问题点

在致细研究其代码后发现如下规则:

1、golang CFB加解密官方示例使用的是AES 128位加密,而在很多语言里目前默认使用的是256位加密。

2、使用的key值,实际是使用的16位ascii码在进行16进制转换后得到的加密串,所以在golang里先使用了hex.DecodeString方法将其转为了明文密码(change this pass)。

3、iv的值是什么是无所谓的,所以在官方示例里,是make给了其16位的byte值,而后通过rand.Reader方法,每次得到的都是一个随机值,这样做的好处就是无规律可循,增加其安全性。

3、加密方法NewCFBEncrypter执行后,得出的是一个二进制数据(stream数据),后面使用的XORKeyStream方法后,可以将二进制数据进行16进制化,这点和大多数语言查到的示例,再base64实例化输出略有不同。当然,这里也支持通过base64方法实例化输出。

4、由于IV值每次都是随机的,所以每次通过XOR方法处理后,进行的输出结果都不同,但并不影响其解密,使用key最终都能正常解密。

5、最终输出的字符串的长度是aes.BlockSize+len(plaintext) 的两倍。

二、python实现

代码如下:

 1#!/usr/bin/env python
 2# code from www.361way.com
 3import base64
 4from Crypto.Cipher import AES
 5#import binascii
 6from binascii import b2a_hex, a2b_hex
 7import os
 8original_message = '{"Command": "free -m"}'
 9a1 = "6368616e676520746869732070617373"
10key = a1.decode("hex")
11#key = 'change this pass'
12iv = os.urandom(16)
13MODE = AES.MODE_CFB
14BLOCK_SIZE = 16
15SEGMENT_SIZE = 128
16def _pad_string(value):
17    length = len(value)
18    pad_size = BLOCK_SIZE - (length % BLOCK_SIZE)
19    return value.ljust(length + pad_size, '\x00')
20def encrypt(key, iv, plaintext):
21    aes = AES.new(key, MODE, iv, segment_size=SEGMENT_SIZE)
22    plaintext = _pad_string(plaintext)
23    encrypted_text = aes.encrypt(plaintext)
24    return encrypted_text
25x = encrypt(key, iv, original_message)
26#encryptedpayload = base64.b64encode(encrypt(key, iv, original_message))
27print type(x)
28print b2a_hex(iv + x)
29#print  binascii.hexlify(x)
30print original_message
31#print encryptedpayload
32#print encryptedpayload.decode()

类似于golang里的hex.DecodeString方法,这里使用的string decode方法,将其转为明文。而iv值,这里通过os.random(16)随机产生16位的值。上面的代码通过encrypt加密后返回的也是二进制乱码,无法正常输出,这里同上面一样,选择使用b2a_hex方法(binascii模块里方法)实现将结果输出为16进制字符串。而注释的base64部分,其实就是常见的实例化方法。

三、java实现

 1import java.util.Base64;
 2import java.util.Scanner;
 3import javax.crypto.Cipher;
 4import javax.crypto.spec.IvParameterSpec;
 5import javax.crypto.spec.SecretKeySpec;
 6/**
 7 * Program to Encrypt/Decrypt String Using AES 128 bit Encryption Algorithm
 8 */
 9public class Tocmd {
10    private static final String encryptionKey           = "6368616e676520746869732070617373";
11    private static final String characterEncoding       = "UTF-8";
12    private static final String cipherTransformation    = "AES/CFB/PKCS5PADDING";
13    private static final String aesEncryptionAlgorithem = "AES";
14public static String unHex(String arg) {
15    String str = "";
16    for(int i=0;i<arg.length();i+=2)
17    {
18        String s = arg.substring(i, (i + 2));
19        int decimal = Integer.parseInt(s, 16);
20        str = str + (char) decimal;
21    }
22    return str;
23}
24public static String bytesToHex(byte[] hashInBytes) {
25        StringBuilder sb = new StringBuilder();
26        for (byte b : hashInBytes) {
27            sb.append(String.format("%02x", b));
28        }
29        return sb.toString();
30    }
31    /**
32     * Method for Encrypt Plain String Data
33     * @param plainText
34     * @return encryptedText
35     */
36    public static String encrypt(String plainText,String skey) {
37        String encryptedText = "";
38        try {
39            Cipher cipher   = Cipher.getInstance(cipherTransformation);
40            byte[] key      = skey.getBytes(characterEncoding);
41            SecretKeySpec secretKey = new SecretKeySpec(key, aesEncryptionAlgorithem);
42            IvParameterSpec ivparameterspec = new IvParameterSpec(key);
43            cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivparameterspec);
44            byte[] cipherText = cipher.doFinal(plainText.getBytes("UTF8"));
45            //Base64.Encoder encoder = Base64.getEncoder();
46            //encryptedText = encoder.encodeToString(cipherText);
47            encryptedText = bytesToHex(key) + bytesToHex(cipherText);
48            //encryptedText = bytesToHex(cipherText);
49        } catch (Exception E) {
50             System.err.println("Encrypt Exception : "+E.getMessage());
51        }
52        return encryptedText;
53    }
54    public static void main(String[] args) {
55        Scanner sc = new Scanner(System.in);
56        System.out.println("Enter String : ");
57        String plainString = sc.nextLine();
58        String skey = unHex(encryptionKey);
59        String encyptStr   = encrypt(plainString,skey);
60        System.out.println("Plain   String  : "+plainString);
61        System.out.println("Encrypt String  : "+encyptStr);
62    }
63}

由于本人不擅长java语言,所以这里的iv值的生成并未修改,这里的IV值使用的固定值,是由key的值搞出来的一个值,当然,想要生成一个16位byte的随机值应该并不麻烦。同样,这里并没有像网上抄来的哪些文章那样,直接base64实例输出,这里使用的unHex和bytesToHex方法,生成了一个类似golang里cfb输出的方法。