前言

最近做项目,发现一些报文需要加密,就简单研究了一些加密算法,包括国密和国际算法,顺便写了一个简单的对称加密算法应用,简单说明原理。实际工作中肯定更复杂,甚至需要专门的硬件配合才行。初步统计的国密和国际加密算法。

  • 国密算法

        SM1、SM2SM3SM4SM7SM9ZUC

        常用的SM2(非对称加密) SM3(摘要算法) SM4(对称加密)

  • 非国密

        RSA、ECC、DH(非对称)MD5、SHA(摘要算法) AES、DES、RC4(对称加密)

准备

一般而言,绝大多数报文和文件加密都是对称加密,毕竟计算资源消耗是非常恐怖的,而且可以通过其他方式增强安全性,比如非对称加密交换对称加密秘钥,秘钥随会话变化而变化,这不就是TLS的设计思想。

Java demo

以DES对称加密为例,但是DES加密的8位秘钥较容易被暴力破解,所以使用3DES加密为例,实际生产一般使用AES或者SM4等。

以外网的一个案例为例,笔者已经找不到最开始从哪里来的了

package com.feng.demo;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

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


/**
 * SecretUtils {3DES加密解密的工具类 }
 * @author William
 * @date 2013-04-19
 */
public class SecretUtils {

    public static void main(String[] args) {
        String originStr = "abc";
        String resultStr = Base64.getEncoder().encodeToString(encryptMode(originStr.getBytes(StandardCharsets.UTF_8)));
        System.out.println(resultStr);
    }

    //定义加密算法,有DES、DESede(即3DES)、Blowfish
    private static final String Algorithm = "DESede";
//    private static final String PASSWORD_CRYPT_KEY = "2012";
    private static final String PASSWORD_CRYPT_KEY = "2012PinganVitality075522628888ForShenZhenBelter075561869839";


    /**
     * 加密方法
     * @param src 源数据的字节数组
     * @return
     */
    public static byte[] encryptMode(byte[] src) {
        try {
            SecretKey deskey = new SecretKeySpec(build3DesKey(PASSWORD_CRYPT_KEY), Algorithm);    //生成密钥
            Cipher c1 = Cipher.getInstance(Algorithm);    //实例化负责加密/解密的Cipher工具类
            c1.init(Cipher.ENCRYPT_MODE, deskey);    //初始化为加密模式
            return c1.doFinal(src);
        } catch (Exception e3) {
            e3.printStackTrace();
        }
        return null;
    }


    /**
     * 解密函数
     * @param src 密文的字节数组
     * @return
     */
    public static byte[] decryptMode(byte[] src) {
        try {
            SecretKey deskey = new SecretKeySpec(build3DesKey(PASSWORD_CRYPT_KEY), Algorithm);
            Cipher c1 = Cipher.getInstance(Algorithm);
            c1.init(Cipher.DECRYPT_MODE, deskey);    //初始化为解密模式
            return c1.doFinal(src);
        } catch (Exception e3) {
            e3.printStackTrace();
        }
        return null;
    }


    /*
     * 根据字符串生成密钥字节数组
     * @param keyStr 密钥字符串
     * @return
     * @throws UnsupportedEncodingException
     */
    public static byte[] build3DesKey(String keyStr) throws UnsupportedEncodingException{
        byte[] key = new byte[24];    //声明一个24位的字节数组,默认里面都是0
        byte[] temp = keyStr.getBytes("UTF-8");    //将字符串转成字节数组

        /*
         * 执行数组拷贝
         * System.arraycopy(源数组,从源数组哪里开始拷贝,目标数组,拷贝多少位)
         */
        //如果temp不够24位,则拷贝temp数组整个长度的内容到key数组中
        //如果temp大于24位,则拷贝temp数组24个长度的内容到key数组中
        System.arraycopy(temp, 0, key, 0, Math.min(key.length, temp.length));
        return key;
    }
}

简单优化了一下代码,简单说明

1. 构建24字节长度的秘钥,3DES刚好是DES的3倍秘钥

2. 传入加密算法和偏移量,这里使用ECB模式,没有偏移量,如果使用CBC模式需要传入IV偏移量

3. 通过base64转为字符串

运行后:

这里有个注意事项,内容和结果必须为block块的整数倍长度,如果不够必须填充,只不过Java包装了,见com.sun.crypto.provider.CipherCore.

prepareInputBuffer

这个Math方法写的看不懂

然后可以看到是通过数组填充

Python demo

其实刚刚的代码运行的好好的,但是,毕竟有Python客户端,甚至有C go等语言,怎么办,自己再写一遍实现,正是这一次重写,发现Java封装了好的隐藏逻辑,比如秘钥一定要大于16位,太短了补0,Java可以运行,但是Python不可以。

简单Python字符串和字节数组处理:

#正常字符串使用''或""表示
# 字符串前b,表示是字节数组  
b = b"aaa"
# 单引号
b = b'aaa'  

 
# str to bytes  
bytes('abc', encoding = 'utf8')  
# an alternative method  
str.encode('abc')  
 
# bytes to str  
str(b, encoding = 'utf-8')  
bytes.decode(b)  

安装des3加密包

pip install pycryptodome

然后根据3DES的规则编写代码

import base64
import sys

from Crypto.Cipher import DES3

#PASSWORD_CRYPT_KEY = '2012'
PASSWORD_CRYPT_KEY = b'2012PinganVitality075522628888ForShenZhenBelter075561869839'

def build_3des_key(key_str, key_len=24) -> bytes:
    length = len(key_str)
    if length < key_len:
        key = key_str + chr(0) * (key_len - length)
        return bytes(key, 'utf-8')
    else:
        return key_str[:key_len]

def build_src_fixed_len(src) -> str:
    delivery_len = 8 - len(src) % 8
    if delivery_len != 0:
        return src + chr(delivery_len) * delivery_len
    return src

def encrypt_mode(src) -> bytes:
    cipher = DES3.new(build_3des_key(PASSWORD_CRYPT_KEY), DES3.MODE_ECB)
    return cipher.encrypt(src)

def decrypt_mode(src):
    cipher = DES3.new(build_3des_key(PASSWORD_CRYPT_KEY), DES3.MODE_ECB)
    return cipher.decrypt(src)

args = sys.argv
print(args)
if args[1] == 'e':
    src = build_src_fixed_len(args[2])
    bytes_src = bytes(src, 'utf-8')
    bytes_res = encrypt_mode(bytes_src)
    bytes_res = base64.encodebytes(bytes_res)
    print(str(bytes_res, encoding = 'utf-8'))
else:
    bytes_src = base64.decodebytes(bytes(args[2], 'utf-8'))
    bytes_res = decrypt_mode(bytes_src)
    print(str(bytes_res, encoding = 'utf-8'))

然后执行命令:python secure_demo.py e 'abc'

可以看到跟Java结果一模一样,解密同理:

这里python有2个注意点,是Java封装后看不出来的:

1. 内容字符串长度必须是8的整数倍

2. 秘钥字符串长度必须超过8位,否则封装的内容是不对的

判断长度,前8位和中间8位;反转判断,如果一样则认为是DES加解密,这个在Java里面封装了处理逻辑,所以可以正常运行

3. 如果不够block的内容,怎么填充呢,是block-长度%block(定义为8)

当然也可以使用class特性,使用main方法运行,各种语言开始趋同进化了。

golang demo

仿照python和Java

package secure

import (
	"crypto/des"
)

const PASSWORD_CRYPT_KEY string = "2012PinganVitality075522628888ForShenZhenBelter075561869839"

func buildSecureKey(key string) []byte {
	bytes := make([]byte, 24)
	copy(bytes, []byte(key))
	//fmt.Println(string(bytes))
	return bytes
}

func BuildSrcBytes(src []byte) []byte {
	length := len(src)
	deliveryLength := des.BlockSize - length%des.BlockSize
	if deliveryLength != 0 {
		deliverySlice := make([]byte, deliveryLength)
		for i := 0; i < deliveryLength; i++ {
			deliverySlice[i] = byte(deliveryLength)
		}
		bytes := append(src, deliverySlice...)
		//fmt.Println(string(bytes))
		return bytes
	}
	//fmt.Println(string(src))
	return src
}

func EncryptMode(src []byte) []byte {
	cipher, _ := des.NewTripleDESCipher(buildSecureKey(PASSWORD_CRYPT_KEY))
	bytesOut := make([]byte, len(src))
	//fmt.Println(src)
	cipher.Encrypt(bytesOut, src)
	//fmt.Println(bytesOut)
	return bytesOut
}

func DecryptMode(src []byte) []byte {
	cipher, _ := des.NewTripleDESCipher(buildSecureKey(PASSWORD_CRYPT_KEY))
	bytesOut := make([]byte, len(src))
	//fmt.Println(src)
	cipher.Decrypt(bytesOut, src)
	return bytesOut
}

然后写个main方法

package main

import (
	"demo/secure"
	"encoding/base64"
	"fmt"
)

func main() {
	originStr := "abc"
	src := secure.BuildSrcBytes([]byte(originStr))
	result := secure.EncryptMode(src)
	fmt.Println(base64.StdEncoding.EncodeToString(result))

	srcBase64 := "dRQLaDw5udI="
	bytes, _ := base64.StdEncoding.DecodeString(srcBase64)
	resOrigin := secure.DecryptMode(bytes)
	fmt.Println(string(resOrigin))
}

执行后跟其他语言一致

总结

实际上仅仅是对称加密,实际上并不安全,不仅面临碰撞攻击,还面临着秘钥保存的问题,根据HTTPS的TLS密码设计,应该动态生成对称加密密钥,使用非对称加密算法交换密钥,根据会话动态变化。但是没有绝对的安全,实际上是加密和性能的取舍问题。非对称加密非常消耗计算性能,但是安全等级很高,对称加密对性能消耗较低,但是安全等级较低。

以TLSv1.2为例,先通过非对称加密(证书)交换对称加密的秘钥,加密回话报文。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐