AES-256加/解密

aes256_encrypt、aes256_decrypt是rose向外提供的aes256加、解密api。

void aes256_encrypt(const tuint8data& key, const tuint8data& iv, const uint8_t* in, uint8_t* out, const int bytes)
{
  // 如果用java写, 对应算法字符串: CIPHER_ALGORITHM = "AES/CBC/NoPadding"
  // 对aes256, 密钥长度必须32字节.
  // 至于分组长度,不论aes256, aes192,还是aes128,分组长度都是16字节(AES_BLOCK_SIZE).
  VALIDATE(key.len == 32 && iv.len == AES_BLOCK_SIZE, null_str);
  bytes是要加密的字节数,要求必须是分组长度的整数倍。
  VALIDATE(bytes > 0 && (bytes % AES_BLOCK_SIZE) == 0, null_str);

  AES_KEY enckey;
  AES_set_encrypt_key(key.ptr, 256, &enckey);

  // iv指初始向量,它的字节数等于分组长度。
  uint8_t iv2[AES_BLOCK_SIZE];
  memcpy(iv2, iv.ptr, iv.len);
  AES_cbc_encrypt(in, out, bytes, &enckey, iv2, AES_ENCRYPT);
}

void aes256_decrypt(const tuint8data& key, const tuint8data& iv, const uint8_t* in, uint8_t* out, const int bytes)
{
  // 如果用java写, 对应算法字符串: CIPHER_ALGORITHM = "AES/CBC/NoPadding"
  // 至于分组长度,不论aes256, aes192,还是aes128,分组长度都是16字节(AES_BLOCK_SIZE).
  VALIDATE(key.len == 32 && iv.len == AES_BLOCK_SIZE, null_str);
  bytes是要解密的字节数,要求必须是分组长度的整数倍。
  VALIDATE(bytes > 0 && (bytes % AES_BLOCK_SIZE) == 0, null_str);

  AES_KEY deckey;
  AES_set_decrypt_key(key.ptr, 256, &deckey);

  uint8_t iv2[AES_BLOCK_SIZE];
  memcpy(iv2, iv.ptr, iv.len);
  AES_cbc_encrypt(in, out, bytes, &deckey, iv.ptr, AES_DECRYPT);
}

以下内容绝大部分抄自“Java使用AES-256加密[1] ”。

要理解AES的加密流程,会涉及到AES的五个关键词:分组密码体制,Padding,初始向量IV,密钥,加密模式。

  • 分组密码体制:所谓分组密码体制就是指将明文切成一段一段的来加密,然后再把一段一段的密文拼起来形成最终密文的加密方式。AES采用分组密码体制,即AES加密会首先把明文切成一段一段的,而且每段数据的长度要求必须是128位16个字节,如果最后一段不够16个字节了, 就需要用Padding来把这段数据填满16个字节,然后分别对每段数据进行加密,最后再把每段加密数据拼起来形成最终的密文。
  • Padding:Padding就是用来把不满16个字节的分组数据填满16个字节用的,它有三种模式PKCS5、PKCS7和NOPADDING. PKCS5是指分组数据缺少几个字节,就在数据的末尾填充几个字节的几,比如缺少5个字节,就在末尾填充5个字节的5。PKCS7是指分组数据缺少几个字节,就在数据的末尾填充几个字节的0,比如缺少7个字节,就在末尾填充7个字节的0。NoPadding是指不需要填充,也就是说数据的发送方肯定会保证最后一段数据也正好是16个字节. 那如果在PKCS5模式下,最后一段数据的内容刚好就是16个16怎么办?那解密端就不知道这一段数据到底是有效数据还是填充数据了,因此对于这种情况, PKCS5模式会自动帮我们在最后一段数据后再添加16个字节的数据, 而且填充数据也是16个16,这样解密段就能知道谁是有效数据谁是填充数据了。PKCS7最后一段数据的内容是16个0, 也是同样的道理。解密端需要使用和加密端同样的Padding模式,才能准确的识别有效数据和填充数据。我们开发通常采用PKCS7 Padding模式。
  • 初始向量IV:初始向量IV的作用是使加密更加安全可靠,我们使用AES加密时需要主动提供初始向量, 而且只需要提供一个初始向量就够了,后面每段数据的加密向量都是前面一段的密文. 初始向量IV的长度规定为128位16个字节, 初始向量的来源为随机生成。
  • 密钥:AES要求密钥的长度可以是128位16个字节、192位或者256位, 位数越高, 加密强度自然越大, 但是加密的效率自然会低一些,因此要做好衡量。我们开发通常采用128位16个字节的密钥,我们使用AES加密时需要主动提供密钥,而且只需要提供一个密钥就够了,每段数据加密使用的都是这一个密钥,密钥来源为随机生成。
  • 加密模式:AES一共有四种加密模式,分别是ECB(电子密码本模式)、CBC(密码分组链接模式)、CFB、OFB,我们一般使用的是CBC模式。四种模式中除了ECB相对不安全之外,其它三种模式的区别并没有那么大。ECB模式是最基本的加密模式, 即仅仅使用明文和密钥来加密数据,相同的明文块会被加密成相同的密文块,这样明文和密文的结构将是完全一样的,就会更容易被破解,相对来说不是那么安全,因此很少使用。CBC模式则比ECB模式多了一个初始向量IV, 加密的时候,第一个明文块会首先和初始向量IV做异或操作,然后再经过密钥加密,然后第一个密文块又会作为第二个明文块的加密向量来异或, 依次类推下去,这样相同的明文块加密出的密文块就是不同的,明文的结构和密文的结构也将是不同的,因此更加安全,我们常用的就是CBC加密模式。

说完AES加密流程,下面说一说aes256_encrypt、aes256_decrypt对应的Java代码。注意用的是AES/CBC/NoPadding,要是用AES/CBC/PKCS5Padding去解码(decode)aes256_encrypt加密出的数据,会返回null。

public class AES256Util {

    /**
     * 密钥, 256位32个字节
     */
    public static final String DEFAULT_SECRET_KEY = "uBdUx82vPHkDKb284d7NkjFoNcKWBuka";

    private static final String AES = "AES";

    /**
     * 初始向量IV, 初始向量IV的长度规定为128位16个字节, 初始向量的来源为随机生成.
     * 初始向量字节数等于分组字字节数. 对aes-256, 虽然密钥是32字节, 但分组长度还是16字节
     */
    // private static final byte[] KEY_VI = "c558Gq0YQK2QUlMc".getBytes();
    private static final byte[] KEY_VI = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0};

    /**
     * 加密解密算法/加密模式/填充方式
     */
    // private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
    private static final String CIPHER_ALGORITHM = "AES/CBC/NoPadding";

    static {
        java.security.Security.setProperty("crypto.policy", "unlimited");
    }

    /**
     * AES加密
     */
    public static byte[] encode(byte[] key, byte[] content) {
        try {
            byte[] key2 = key != null? key: DEFAULT_SECRET_KEY.getBytes();
            javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key2, AES);
            javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(KEY_VI));

            // 根据密码器的初始化方式加密
            return cipher.doFinal(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * AES解密
     */
    public static byte[] decode(byte[] key, byte[] content) {
        try {
            byte[] key2 = key != null? key: DEFAULT_SECRET_KEY.getBytes();
            javax.crypto.SecretKey secretKey = new javax.crypto.spec.SecretKeySpec(key2, AES);
            javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, new javax.crypto.spec.IvParameterSpec(KEY_VI));

            // 解密
            return cipher.doFinal(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

全部评论: 0

    写评论: