案例
采用 AES 对称加密。对于加密密钥,直接将字符审强制转换为 128byte的二进制使用的。其中:
加密算法: AES-128 CBC 方式
初始化向量 IV: 和加密密钥相同
BlockSize: 16
补码方式: PKCS5Padding (padding byte=blockSize len(待加密字符串)%blockSize)加密结果编码方式:16 进制
示例
原价格: 2000(单位是分/CPM)
待加密字符串:"2000"
加密密匙:"6213FC1A2C51C632" (正式密钥)
加密后字符串:"b9996b4587b67ef45f2336c963f991c0"
解析
- AES: 加解密算法
- CBC: 数据分组模式
- PKCS5Padding: 数据按照一定的大小进行分组,最后分剩下那一组,不够长度,就需要进行补齐
拿到一个原始数据以后,首先需要对数据进行分组,分组以后如果长度不满足分组条件,需要进行补齐,最后形成多个分组,在使用加解密算法,对这多个分组进行加解密。所以这个过程中,AES,CBC,PKCS5Padding 缺一不可
IV偏移量设置: ECB模式不需要设置, CBC模式需要设置
ECB
模式的分组强烈不建议使用
如果是 JAVA 语言指定算法,写的是 AES/CBC/PKCS5Padding
PHP实现
示例:
function pkcsPadding($str, $blocksize)
{
$pad = $blocksize - (strlen($str) % $blocksize);
return $str . str_repeat(chr($pad), $pad);
}
function unPkcsPadding($str)
{
$pad = ord($str{strlen($str) - 1});
if ($pad > strlen($str)) {
return false;
}
return substr($str, 0, -1 * $pad);
}
$key = "6213FC1A2C51C632";
$method = "AES-128-CBC";
$ivlen = openssl_cipher_iv_length($method);
echo "---{$method}--iv length must be: $ivlen \n";
echo "---key({$key})--length is ".strlen($key)."\n";
$str = pkcsPadding("2000", 16);
$encrypted = openssl_encrypt($str, $method, $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $key);
$encrypted = bin2hex($encrypted);
echo "-------encrypted--------:";
echo $encrypted . "\n";
$destr = openssl_decrypt(hex2bin($encrypted), $method, $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $key);
echo "-------decrypt--------:";
echo unPkcsPadding($destr)."\n";
输出:
---AES-128-CBC--iv length must be: 16
---key(6213FC1A2C51C632)--length is 16
-------encrypted--------:b9996b4587b67ef45f2336c963f991c0
-------decrypt--------:2000
Go实现
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"errors"
"fmt"
"os"
)
var paddingErr error = errors.New("Error Padding")
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS5UnPadding(origData []byte, blockSize int) ([]byte, error) {
length := len(origData)
// 去掉最后一个字节 unpadding 次
unpadding := int(origData[length-1])
if unpadding > 0 && unpadding <= blockSize {
return origData[:(length - unpadding)], nil
}
return origData[:], paddingErr
}
func AesCBCEncrypte(s []byte, key string) string {
keybytes := []byte(key)
plaintext := PKCS5Padding([]byte(s), aes.BlockSize)
block, _ := aes.NewCipher(keybytes[:aes.BlockSize])
mode := cipher.NewCBCEncrypter(block, keybytes[:aes.BlockSize])
crypted := make([]byte, len(plaintext))
mode.CryptBlocks(crypted, plaintext)
return string(hex.EncodeToString(crypted))
}
func AesCBCDecrypte(decrypte string, key string) (string, error) {
defer func() {
if err := recover(); err != nil {
fmt.Fprintf(os.Stderr, "error string:%s key:%s err:%v\n", decrypte, key, err)
}
}()
if (len(decrypte) % (aes.BlockSize * 2)) != 0 {
return "", errors.New("decrypte data too short")
}
keybytes := []byte(key)
decrypteData, err := hex.DecodeString(decrypte)
if err != nil {
return "", errors.New("decrypte data format error")
}
block, _ := aes.NewCipher(keybytes[:aes.BlockSize])
mode := cipher.NewCBCDecrypter(block, keybytes[:aes.BlockSize])
decryptedData := make([]byte, len(decrypteData))
mode.CryptBlocks(decryptedData, decrypteData)
decryptedData, err = PKCS5UnPadding(decryptedData, aes.BlockSize)
if err != nil {
return "", err
}
return string(decryptedData), nil
}
func main() {
data := "2000"
key := "6213FC1A2C51C632"
encrypt := AesCBCEncrypte([]byte(data), key)
fmt.Println("encrypt:", encrypt)
decrypt, _ := AesCBCDecrypte(encrypt, key)
fmt.Println("decrypt:", decrypt)
}
运行输出:
encrypt: b9996b4587b67ef45f2336c963f991c0
decrypt: 2000
JAVA实现
package com.adget.server.common;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* AES 是一种可逆加密算法,对用户的敏感信息加密处理 对原始数据进行AES加密后,在进行Base64编码转化
*/
public class AesCBCUtils {
private static String encrypt(byte[] sSrc, String sKey) throws Exception {
if (sKey == null || sSrc == null) {
return null;
}
return byte2hex(encryptByte(sSrc, sKey)).toLowerCase();
}
// 加密
public static String encrypt(String sSrc, String sKey) throws Exception {
if (sKey == null || sSrc == null) {
return null;
}
return encrypt(sSrc.getBytes(), sKey);
}
private static byte[] encryptByte(byte[] sSrc, String sKey) throws Exception {
if (sKey == null || sSrc == null) {
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
return null;
}
byte[] raw = sKey.getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(sKey.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
return cipher.doFinal(sSrc);
}
private static String decrypt(byte[] sSrc, String sKey) throws Exception {
if (sKey == null || sSrc == null) {
return null;
}
return new String(decryptByte(sSrc, sKey));
}
private static byte[] decryptByte(byte[] sSrc, String sKey) throws Exception {
try {
// 判断Key是否正确
if (sKey == null || sSrc == null) {
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
return null;
}
byte[] raw = sKey.getBytes();// "ASCII"
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(sKey.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
try {
return cipher.doFinal(sSrc);
} catch (Exception e) {
return null;
}
} catch (Exception ex) {
return null;
}
}
// 解密
public static String decrypt(String sSrc, String sKey) throws Exception {
if (sKey == null || sSrc == null) {
return null;
}
return decrypt(hex2byte(sSrc), sKey);
}
private static byte[] hex2byte(String strhex) {
if (strhex == null) {
return null;
}
int l = strhex.length();
if (l % 2 == 1) {
return null;
}
byte[] b = new byte[l / 2];
for (int i = 0; i != l / 2; i++) {
b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2), 16);
}
return b;
}
private static String byte2hex(byte[] b) {
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
if (stmp.length() == 1) {
hs = hs + "0" + stmp;
} else {
hs = hs + stmp;
}
}
return hs.toUpperCase();
}
public static void main(String[] arg) {
try {
// 需要加密的字串
String cSrc = "2000";
String sKey = "d6797b0f95f8ead1";
System.out.println("加密前的字串是:" + cSrc);
// 加密
String enString = encrypt(cSrc, sKey);
System.out.println("加密后的字串是:" + enString);
// 解密
String DeString = decrypt(enString, sKey);
System.out.println("解密后的字串是:" + DeString);
} catch (Exception e) {
e.printStackTrace();
}
}
}
DES对称加密介绍 http://blog.catmes.com/archives/des-encrypt.html
AES/CBC/PKCS5Padding 到底是什么 https://xie.infoq.cn/article/7e93850ddb9a6170bd49cd28c
PHP 报错openssl_encrypt(): IV passed is 32 bytes long... https://blog.csdn.net/Anne_01/article/details/128040891