使用 AES 对称加密算法 CTR 模式进行加解密

Published: 2020-02-23

Tags: AES

本文总阅读量

概要

之前在网上找过 Javascript 的 aes-js 模块来对数据进行过加密解密,后来想用其它语言来解密,发现自己对对称密码的了解有限,试错几次后,还是要先了解一下 AES才好,这篇文章用于记录 AES 是什么,以及在 Rust,Javascript,Python3 中使用 AES-CTR 模式进行加解密,以便能够在不同的场景下使用一种语言加密后的数据可以用另一种语言解密。

AES 简介

摘自《图解密码技术》第 65 页。

AES(Advanced Encryption Standard)是取代其前任标准(DES)而成为新标准的一种对称密码算法。全世界的企业和密码学家提交了多个对称密码做为 AES 的候选,最终在 2000 年从这些候选算法中选出了一种名为 Rijndael 的对称密码算法,并将其确定位了 AES。

参加 AES 竞选是有条件的,这个条件就是:被选为 AES 的算法必须无条件地免费供全世界使用。

候选算法

经过众多世界上最高水平密码学家共同尝试破译,仍未找到其弱点的算法,结合速度,实现难易程度等方面后,进入候选的有如下几种算法。

名称 提交者
MARS IBM 公司
RC6 RSA 公司
Rijndael Daemen,Rijmen
Serpent Anderson,Biham,Knudsen
Twofish Counterpane 公司

最终,Rijndael 技压群雄,成为了 AES 的算法,被全世界使用。

AES 及 Rijndael 更为详尽的资料

  1. Understanding AES & Rijndael

分组密码模式

分组密码模式有 ECBCBCCFBOFBCTR。其中最推荐使用 CBCCTR两种模式,不应该使用 ECB 模式。

加解密(CRT 模式)

需要注意的是,实际使用中 key 与 count 值应该动态生成,即每个用户有自己的随机值。

Rust

借助 aes-ctr 模块,cargo-add add aes-ctr

use aes_ctr::Aes128Ctr;
use aes_ctr::stream_cipher::generic_array::{typenum::U16, GenericArray};
use aes_ctr::stream_cipher::{
    NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek
};
use std::str;

fn main() {

    // 需要加解密的字符串
    let mut message = String::from("hello world!");
    println!("Input: {:?}", &message);

    unsafe {
        // let mut data = [1, 2, 3, 4, 5, 6, 7, 8, 8, 8];
        let mut data = &mut message.as_bytes_mut();

        // 由字符串生成key也可以自己指定
        let key = GenericArray::from_slice(b"very secret key.");
        // let u8arraykey: [u8; 16] = [118, 101, 114, 121, 32, 115, 101, 99, 114, 101, 116, 32, 107, 101, 121, 46];
        // let key: &GenericArray<u8, U16> = GenericArray::from_slice(&u8arraykey);
        // let strdata = str::from_utf8(&u8arraykey).expect("Found invalid UTF-8");
        // println!("{:?}", &strdata);

        // 生成随机数
        let u8arraynonce: [u8; 16] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5];
        let nonce: &GenericArray<u8, U16> = GenericArray::from_slice(&u8arraynonce);
        //let nonce = GenericArray::from_slice(b"and secret nonce");

        // 创建cipher实例
        let mut cipher = Aes128Ctr::new(&key, &nonce);

        // 加密
        cipher.apply_keystream(&mut data);

        // bytes转hex
        let encrypt_hex = hex::encode(&data);
        println!("Encrypt: {:?}", &encrypt_hex);

        // hex转bytes
        let mut encrypt_data = hex::decode(&encrypt_hex).expect("Decoding failed");

        // 解密
        cipher.seek(0);
        cipher.apply_keystream(&mut encrypt_data);

        // u8数组转字符串
        let ori_message = str::from_utf8(&encrypt_data).expect("Found invalid UTF-8");
        println!("Decrypt: {:?}", &ori_message);
    }
}

Output

[dong@nuc5 aes_ctr_rust]$ time ./target/release/aes_ctr_rust 
Input: "hello world!"
Encrypt: "7fde6165801ce33262605b14"
Decrypt: "hello world!"

real    0m0.003s
user    0m0.001s
sys 0m0.002s

Javascript

借助 aes-js 模块,yarn add aes-js

const aesjs = require('aes-js');

function aes_encrypt(key, text) {
    var textBytes = aesjs.utils.utf8.toBytes(text);

    var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5));
    var encryptedBytes = aesCtr.encrypt(textBytes);

    var encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes);
    return encryptedHex;
}

function aes_decrypt(key, encryptedHex) {
    var encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex);

    var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5));
    var decryptedBytes = aesCtr.decrypt(encryptedBytes);

    var decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes);
    return decryptedText;
}

let text = "hello world!"
let key = [118, 101, 114, 121, 32, 115, 101, 99, 114, 101, 116, 32, 107, 101, 121, 46]
console.log("Input: " + text)
let encrypt_text = aes_encrypt(key, text)
console.log("Encrypt: " + encrypt_text)
let decrypt_text = aes_decrypt(key, encrypt_text)
console.log("Decrypt: " + decrypt_text)

Output

[dong@nuc5 aes_ctr_js]$ time node main.js 
Input: hello world!
Encrypt: 7fde6165801ce33262605b14
Decrypt: hello world!

real    0m0.050s
user    0m0.043s
sys 0m0.007s

Python3

# -*- coding: utf-8 -*-
from Crypto.Cipher import AES
from Crypto import Random
from Crypto.Util import Counter
import binascii

# 初始化key与count
key = "very secret key."
iv = '5'

# 数据
plaintext = b"hello world!"
print(plaintext)

# 加密
ctr_e = Counter.new(128, initial_value=int(iv, 16))
encryptor = AES.new(key, AES.MODE_CTR, counter=ctr_e)
ciphertext = encryptor.encrypt(plaintext)
print(binascii.hexlify(ciphertext))

# 解密
ctr_d = Counter.new(128, initial_value=int(iv, 16))
decryptor = AES.new(key, AES.MODE_CTR, counter=ctr_d)
decoded_text = decryptor.decrypt(ciphertext)
print(decoded_text)

Output

[dong@nuc5 aes_ctr_python3]$ time python3 main.py 
b'hello world!'
b'7fde6165801ce33262605b14'
b'hello world!'

real    0m0.050s
user    0m0.042s
sys 0m0.007s

编译型语言 Rust 果然还是给力的,时间花费只有解释型语言 Node.js 和 Python3 的十分之一。

参考

  1. 《图解密码技术》
  2. How can I convert a hex string to a u8 slice?
  3. How to use GenericArray?
  4. How do I convert a Vector of bytes (u8) to a string
  5. https://docs.rs/aes-ctr/0.3.0/aes_ctr/
  6. https://github.com/ricmoo/aes-js