java AES/ECB/PKCS5padding 加密 转换Python3

4,278 阅读4分钟

java AES/ECB/PKCS5padding 加密 转换Python

注意:加密方式128位AES加密模式为ECB进行加密,加密后使用Base64转码。

最近和java项目对接遇到AES加密算法,java代码有SecureRandom.getInstance("SHA1PRNG"); 转换成Python

AES加密

高级加密标准(Advanced Encryption Standard: AES)是美国国家标准与技术研究院(NIST)在2001年建立了电子数据的加密规范。
其是对称加解密算法的最经典算法之一,它是一种分组加密标准,每个加密块大小为128位,允许的密钥长度为128、192和256位。这里只介绍ECB加密模式。

AES加密模式:ECB/CBC/CTR/OFB/CFB
填充:pkcs5padding/pkcs7padding/zeropadding/iso10126/ansix923
数据块:128位/192位/256位

ECB模式

ECB模式作为最简单的工作模式,直接将明文分组,每组分别加密,每个分组独立且前后文无关

Java 默认加密demo

Java默认AES加密模式是"AES/ECB/PKCS5Padding"。

java代码展示

package com.qzd.hit.util;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class Demo {

	/*** main
    * @author qizai
    * data: params
    * encStr: Here, the length of the key must be a multiple of 16
    */
	public static void main(String[] args) throws Exception {
		// Encrypt Data
        String key = "ROcb6JybnKILLOlO"; // Secret Key length must be (16 24 32)
		String data = "[\"123456789\"]"; // Content to be encrypted
		String encStr = encrypt(data, key);  // Content to be decrypted
		System.out.println(encStr); //Encrypted data: w6oijM0xddQrItnH3UybLQ==
		// Decrypt Data
		String decStr = decrypt(encStr, key);
		System.out.println(decStr); //Decrypted data: ["123456789"]
	}

	public static String encrypt(String content, String password) {
		if (StringUtils.isEmpty(content) || StringUtils.isEmpty(password)) {
			System.out.println("AES encryption params is null");
			return null;
		}
		try {
			// Create cipher
			Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
			byte[] byteContent = content.getBytes("UTF-8");
			// Initialize as cryptographic cipher
			cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(password));
			byte[] encryptByte = cipher.doFinal(byteContent);
			return org.apache.commons.codec.binary.Base64.encodeBase64String(encryptByte);
		} catch (Exception e) {
			System.out.println("AES encryption operation has exception,content:{},password:{}", content, password, e);
		}
		return null;
	}

	public static String decrypt(String encryptContent, String password) throws Exception {
		if (StringUtils.isEmpty(encryptContent) || StringUtils.isEmpty(password)) {
			System.out.println("AES The request parameter is null");
			return null;
		}
		Cipher cipher = null;
		cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
		// Set to decryption mode
		cipher.init(Cipher.DECRYPT_MODE, getSecretKey(password));
		// Perform decryption operation
		byte[] result = cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(encryptContent));
		return new String(result, "UTF-8");
	}

	private static SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException {
		// Generates the generator for the specified algorithm key
		KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
		SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
		random.setSeed(password.getBytes());
		keyGenerator.init(128, random);
		// Grnerate Key
		SecretKey secretKey = keyGenerator.generateKey();
		// Key converted to AES
		return new SecretKeySpec(secretKey.getEncoded(), "AES");
	}
}

加密注意点:

KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); // Generates the generator for the specified algorithm key
SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); // SHA1PRNG Random number algorithm
random.setSeed(password.getBytes());
keyGenerator.init(128, random);
SecretKey secretKey = keyGenerator.generateKey();

Python 实现AES/ECB/PKCS5padding

安装AES相关库(window下)

pip install pycryptodome

Linux下安装

pip install pycrypto

相关代码实现步骤

import base64
import hashlib

import requests
from Crypto.Cipher import AES as _AES


class AES:

    def __init__(self, key: str):
        """Init aes object used by encrypt or decrypt.
        AES/ECB/PKCS5Padding  same as aes in java default.
        """

        self.aes = _AES.new(self.get_sha1prng_key(key), _AES.MODE_ECB)

    @staticmethod
    def get_sha1prng_key(key: str) -> bytes:
        """encrypt key with SHA1PRNG.
        same as java AES crypto key generator SHA1PRNG.
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );
        secureRandom.setSeed(decryptKey.getBytes());
        keygen.init(128, secureRandom);
        :param string key: original key.
        :return bytes: encrypt key with SHA1PRNG, 128 bits or 16 long bytes.
        """

        signature: bytes = hashlib.sha1(key.encode()).digest()
        signature: bytes = hashlib.sha1(signature).digest()
        return signature[:16]

    @staticmethod
    def padding(s: str) -> str:
        """Padding PKCS5"""

        pad_num: int = 16 - len(s) % 16
        return s + pad_num * chr(pad_num)

    @staticmethod
    def unpadding(s):
        """Unpadding PKCS5"""

        padding_num: int = ord(s[-1])
        return s[: -padding_num]

    def encrypt_to_bytes(self, content_str):
        """From string encrypt to bytes ciphertext.
        """

        content_bytes = self.padding(content_str).encode()
        ciphertext_bytes = self.aes.encrypt(content_bytes)
        return ciphertext_bytes

    def encrypt_to_base64(self, content_str):
        """From string encrypt to base64 ciphertext.
        """

        ciphertext_bytes = self.encrypt_to_bytes(content_str)
        ciphertext_bs64 = base64.b64encode(ciphertext_bytes).decode()
        return ciphertext_bs64

    def decrypt_from_bytes(self, ciphertext_bytes):
        """From bytes ciphertext decrypt to string.
        """

        content_bytes = self.aes.decrypt(ciphertext_bytes)
        content_str = self.unpadding(content_bytes.decode())
        return content_str

    def decrypt_from_base64(self, ciphertext_bs64):
        """From base64 ciphertext decrypt to string.
        """

        ciphertext_bytes = base64.b64decode(ciphertext_bs64)
        content_str = self.decrypt_from_bytes(ciphertext_bytes)
        return content_str


def encrypt_to_bytes(content_str, encrypt_key: str):
    """From string encrypt to bytes ciphertext.
    """

    aes: AES = AES(encrypt_key)
    ciphertext_bytes = aes.encrypt_to_bytes(content_str)
    return ciphertext_bytes


def encrypt_to_base64(content_str, encrypt_key: str) -> str:
    """From string encrypt to base64 ciphertext.
    """

    aes: AES = AES(encrypt_key)
    ciphertext_bs64 = aes.encrypt_to_base64(content_str)
    return ciphertext_bs64


def decrypt_from_bytes(ciphertext_bytes, decrypt_key: str) -> str:
    """From bytes ciphertext decrypt to string.
    """

    aes: AES = AES(decrypt_key)
    content_str = aes.decrypt_from_bytes(ciphertext_bytes)
    return content_str


def decrypt_from_base64(ciphertext_bs64, decrypt_key: str) -> str:
    """From base64 ciphertext decrypt to string.
    """

    aes: AES = AES(decrypt_key)
    content_str = aes.decrypt_from_base64(ciphertext_bs64)
    return content_str


if __name__ == "__main__":
    key = "iC6qfFyiza8aq"
    encrypt_str_data = "[\"13811111111\"]"
    et = encrypt_to_base64(encrypt_str_data, key)
    print("加密后数据: ", et)
    ret = decrypt_from_base64(et, key)
    print("解密后数据: ", ret)

关键代码

该方法实现了对key的转换,java中关键加密代码的内容,在这里返回为16进制字符串。

    @staticmethod
    def get_sha1prng_key(key: str) -> bytes:
        """encrypt key with SHA1PRNG.
        same as java AES crypto key generator SHA1PRNG.
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );
        secureRandom.setSeed(decryptKey.getBytes());
        keygen.init(128, secureRandom);
        :param string key: original key.
        :return bytes: encrypt key with SHA1PRNG, 128 bits or 16 long bytes.
        """

        signature: bytes = hashlib.sha1(key.encode()).digest()
        signature: bytes = hashlib.sha1(signature).digest()
        return signature[:16]

简约版

import base64
import hashlib

from Crypto.Cipher import AES

SECRET_KEY = 'iC6qfFyiza8aq'  # 密钥


class AesEncrypt(object):
    def __init__(self):
        self.key = self.getKey(SECRET_KEY)
        self.mode = AES.MODE_ECB

    def pading(self, text):
        """对加密字符的处理"""
        return text + (len(self.key) - len(text) % len(self.key)) * chr(len(self.key) - len(text) % len(self.key))

    def unpading(self, text):
        """对解密字符的处理"""
        return text[0:-ord(text[-1:])]

    def getKey(self, key):
        """对key的处理,key 的长度 16,24,32"""
        signature: bytes = hashlib.sha1(key.encode()).digest()
        signature: bytes = hashlib.sha1(signature).digest()
        return signature[:16]

    def encrypt(self, text):
        """加密函数"""
        ecb_encrypt = AES.new(self.key, self.mode)  # ECB 模式
        ecb_text = ecb_encrypt.encrypt(bytes(self.pading(text), encoding="utf8"))
        encrypt_string = base64.b64encode(ecb_text).decode()
        return encrypt_string

    def decrypt(self, text):
        """解密函数"""
        decode = base64.b64decode(text)
        ecb_decrypt = AES.new(self.key, self.mode)  # ECB 模式
        plain_text = ecb_decrypt.decrypt(decode)
        decrypt_string = self.unpading(plain_text).decode()
        return decrypt_string


if __name__ == '__main__':
    aes_encrypt = AesEncrypt()
    data = "[\"13811111111\"]"
    en = aes_encrypt.encrypt(data)
    print("加密数据: ", en)
    dr = aes_encrypt.decrypt(en)
    print("解密数据: ", dr)

1. 对key进行sha1prng加密,就是文中的get_sha1prng_key方法,拿到加密后的密文当做AES的key

2. 确定AES的加密方式ECB,CBC 等 AES加密模式:ECB/CBC/CTR/OFB/CFB

3. 填充,key和原始文本都有可能填充,NoPadding,不填充,0填充,还有pkcs5padding, 不填充就是不对内容填充,直接加密,上面代码实现了\0填充和pkcs5padding 。

4. 总的来说加密内容对不上基本是key处理不一样或者填充不对。

【参考SHA1算法】