【GoCN酷Go推荐】crypto 官方加解密库

共 21600字,需浏览 44分钟

 ·

2021-08-05 06:02

1. 为什么用官方crypto,不用第三方库

因为这个加解密库实际很简单,并不需要太多的配置以及学习;且特别是比较敏感的数据,如果代码圈发现有一种算法的加密方式是有漏洞的,第三方库真不一定会及时解决。

2. 是什么原因导致我去用它

最近对接华立电表与立方停车,哎,看着java代码发呆,还望这两家公司提供多语言的SDK,特别是这几年日渐火热的golang。

3. 怎么使用

我先上java代码吧,这样比较有对比性:

AESUtils.java

package src.com.first;

import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * AES加密类
 *
 */

public class AESUtils {
    /**
     * 密钥算法
     */

    private static final String KEY_ALGORITHM = "AES";

    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";

    /**
     * 初始化密钥
     *
     * @return byte[] 密钥
     * @throws Exception
     */

    public static byte[] initSecretKey() {
        // 返回生成指定算法的秘密密钥的 KeyGenerator 对象
        KeyGenerator kg = null;
        try {
            kg = KeyGenerator.getInstance(KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return new byte[0];
        }
        // 初始化此密钥生成器,使其具有确定的密钥大小
        // AES 要求密钥长度为 128
        kg.init(128);
        // 生成一个密钥
        SecretKey secretKey = kg.generateKey();
        return secretKey.getEncoded();
    }

    /**
     * 转换密钥
     *
     * @param key
     *            二进制密钥
     * @return 密钥
     */

    public static Key toKey(byte[] key) {
        // 生成密钥
        return new SecretKeySpec(key, KEY_ALGORITHM);
    }

    /**
     * 加密
     *
     * @param data
     *            待加密数据
     * @param key
     *            密钥
     * @return byte[] 加密数据
     * @throws Exception
     */

    public static byte[] encrypt(byte[] data, Key key) throws Exception {
        return encrypt(data, key, DEFAULT_CIPHER_ALGORITHM);
    }

    /**
     * 加密
     *
     * @param data
     *            待加密数据
     * @param key
     *            密钥
     * @param cipherAlgorithm
     *            加密算法/工作模式/填充方式
     * @return byte[] 加密数据
     * @throws Exception
     */

    public static byte[] encrypt(byte[] data, Key key, String cipherAlgorithm) throws Exception {
        // 实例化
        Cipher cipher = Cipher.getInstance(cipherAlgorithm);
        // 使用密钥初始化,设置为加密模式
        cipher.init(Cipher.ENCRYPT_MODE, key);
        // 执行操作
        return cipher.doFinal(data);
    }

    /**
     * 解密
     *
     * @param data
     *            待解密数据
     * @param key
     *            密钥
     * @return byte[] 解密数据
     * @throws Exception
     */

    public static byte[] decrypt(byte[] data, Key key) throws Exception {
        return decrypt(data, key, DEFAULT_CIPHER_ALGORITHM);
    }

    /**
     * 解密
     *
     * @param data
     *            待解密数据
     * @param key
     *            密钥
     * @param cipherAlgorithm
     *            加密算法/工作模式/填充方式
     * @return byte[] 解密数据
     * @throws Exception
     */

    public static byte[] decrypt(byte[] data, Key key, String cipherAlgorithm) throws Exception {
        // 实例化
        Cipher cipher = Cipher.getInstance(cipherAlgorithm);
        // 使用密钥初始化,设置为解密模式
        cipher.init(Cipher.DECRYPT_MODE, key);
        // 执行操作
        return cipher.doFinal(data);
    }

    public static String showByteArray(byte[] data) {
        if (null == data) {
            return null;
        }
        StringBuilder sb = new StringBuilder("{");
        for (byte b : data) {
            sb.append(b).append(",");
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.append("}");
        return sb.toString();
    }

    /**
     * 将16进制转换为二进制
     *
     * @param hexStr
     * @return
     */

    public static byte[] parseHexStr2Byte(String hexStr) {

        if (hexStr.length() < 1)
            return null;
        byte[] result = new byte[hexStr.length() / 2];
        for (int i = 0; i < hexStr.length() / 2; i++) {
            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }

    /**
     * 将二进制转换成16进制
     *
     * @param buf
     * @return
     */

    public static String parseByte2HexStr(byte buf[]) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    public static void main(String[] args) throws Exception {
        /*byte[] key = initSecretKey();
        System.out.println("key:" + Base64.getEncoder().encode(key));
        System.out.println("key:" + showByteArray(key));*/


        // 指定key
        String kekkk = "cmVmb3JtZXJyZWZvcm1lcg==";
        System.out.println("kekkk:" + showByteArray(Base64.getDecoder().decode(kekkk)));
        Key k = toKey(Base64.getDecoder().decode(kekkk));


        String data = "{\"carCode\":\"川A07E0M\",\"inTime\":\"2021-07-27 13:35:10\",\"passTime\":\"2021-07-27 16:50:34\",\"parkID\":\"88\",\"inOrOut\":\"1\",\"GUID\":\"f025e064c1864af68406c797b0999c70\",\"channelID\":\"2\",\"channelName\":\"1号门停车场(出场通道1)\",\"imagePath\":\"http://192.168.0.101:9988\\\\Capture_Images\\\\20210727\\\\川A07E0M\\\\川A07E0M_20210727165034406.jpg\"}";
        System.out.println("加密前数据: string:" + data);
        System.out.println("加密前数据: byte[]:" + showByteArray(data.getBytes("utf-8")));
        System.out.println();

        byte[] encryptData = encrypt(data.getBytes("utf-8"), k);
        String encryptStr=parseByte2HexStr(encryptData);

        System.out.println("加密后数据: byte[]:" + showByteArray(encryptData));
        System.out.println("加密后数据: Byte2HexStr:" + encryptStr);
        System.out.println();

        byte[] decryptData = decrypt(parseHexStr2Byte(encryptStr), k);
        System.out.println("解密后数据: byte[]:" + showByteArray(decryptData));
        System.out.println("解密后数据: string:" + new String(decryptData,"utf-8"));

    }
}

开始上对比的golang代码,请注意看我的注释:

package lib

import (
 "bytes"
 "crypto/aes"
 "encoding/base64"
 "encoding/hex"
 "errors"
 "strings"
)

type liFangEncryptionInterface interface {
 LiFangEncrypt() (string, error) // 立方停车加密
 LiFangDecrypt() ([]byte, error) // 立方停车解密
}

type LiFangEncryptionStruct struct {
 Key               string // 立方密钥
 NeedEncryptString string // 需要加密的字符串
 NeedDecryptString string // 需要解密的字符串
}

// NewLiFangEncryption 创建立方加解密对象
func NewLiFangEncryption(lfs *LiFangEncryptionStruct) liFangEncryptionInterface {
 return &LiFangEncryptionStruct{
  Key:               lfs.Key,
  NeedEncryptString: lfs.NeedEncryptString,
  NeedDecryptString: lfs.NeedDecryptString,
 }
}

func (lfs *LiFangEncryptionStruct) LiFangEncrypt() (encodeStr string, err error) {
 decodeKey, err := base64.StdEncoding.DecodeString(lfs.Key) //这一行是说,将Key密钥进行base64编码,这一行与加密 AES/ECB/PKCS5Padding 没有关系
 aseByte, err := aesEncrypt([]byte(lfs.NeedEncryptString), decodeKey)//这一行开始就是 AES/ECB/PKCS5Padding 的标准加密了
 encodeStr = strings.ToUpper(hex.EncodeToString(aseByte)) //把加密后的字符串变为大写
 return
}

func (lfs *LiFangEncryptionStruct) LiFangDecrypt() (lastByte []byte, err error) {
 hexStrByte, err := hex.DecodeString(lfs.NeedDecryptString) //这一行的意思,把需要解密的字符串从16进制字符转为2进制byte数组
 decodeKey, err := base64.StdEncoding.DecodeString(lfs.Key) //这行还是将Key密钥进行base64编码
 lastByte, err = aesDecrypt(hexStrByte, decodeKey) // 这里开始就是 AES/ECB/PKCS5Padding 的标准解密了
 return
}

func aesEncrypt(src, key []byte) ([]byte, error) {
 block, err := aes.NewCipher(key) // 生成加密用的block对象
 if err != nil {
  return nil, err
 }
 bs := block.BlockSize() // 根据传入的密钥,返回block的大小,也就是俗称的数据块位数,如128位,192位,256位
 src = pKCS5Padding(src, bs)// 这里是PKCS5Padding填充方式,继续向下看
 if len(src)%bs != 0 { // 如果加密字符串的byte长度不能整除数据块位数,则表示当前加密的块大小不适用
  return nil, errors.New("Need a multiple of the blocksize")
 }
 out := make([]bytelen(src))
 dst := out
 for len(src) > 0 {
  block.Encrypt(dst, src[:bs]) // 开始用已经产生的key来加密
  src = src[bs:]
  dst = dst[bs:]
 }
 return out, nil
}

func aesDecrypt(src, key []byte) ([]byte, error) {
 block, err := aes.NewCipher(key)
 if err != nil {
  return nil, err
 }

 out := make([]bytelen(src))
 dst := out
 bs := block.BlockSize()
 if len(src)%bs != 0 {
  return nil, errors.New("crypto/cipher: input not full blocks")
 }
 for len(src) > 0 {
  block.Decrypt(dst, src[:bs])
  src = src[bs:]
  dst = dst[bs:]
 }
 out = pKCS5UnPadding(out)
 return out, nil
}

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) []byte {
 length := len(origData)
 unpadding := int(origData[length-1])
 return origData[:(length - unpadding)]
}

之后附赠普通带偏移量的AES加解密   AES/CBC/PKCS5Padding 的代码:

// aesEncrypt 加密
func (hs *HuaLiServiceStruct) aesEncrypt() (string, error) {
 key := []byte(hs.DataSecret)
 encodeBytes := []byte(hs.EncodeStr)
 //根据key密钥 生成密文
 block, err := aes.NewCipher(key)
 if err != nil {
  return "", err
 }
 blockSize := block.BlockSize()
 encodeBytes = pKCS5Padding(encodeBytes, blockSize)
 //添加偏移量
 blockMode := cipher.NewCBCEncrypter(block, []byte(hs.DataSecretIV)) // 其实这里就更简单了,只需要创建一个CBC的加密对象,传入偏移量即可
 crypted := make([]bytelen(encodeBytes))
 blockMode.CryptBlocks(crypted, encodeBytes)
 return base64.StdEncoding.EncodeToString(crypted), nil
}

// aesDecrypt 解密
func (hs *HuaLiServiceStruct) aesDecrypt() ([]byte, error) {
 key := []byte(hs.DataSecret)
 //先解密base64
 decodeBytes, err := base64.StdEncoding.DecodeString(hs.DecodeStr)
 if err != nil {
  return nil, err
 }
 block, err := aes.NewCipher(key)
 if err != nil {
  return nil, err
 }
 blockMode := cipher.NewCBCDecrypter(block, []byte(hs.DataSecretIV)) // 其实这里就更简单了,只需要创建一个CBC的解密对象,传入偏移量即可
 origData := make([]bytelen(decodeBytes))

 blockMode.CryptBlocks(origData, decodeBytes)
 origData = pKCS5UnPadding(origData)
 return origData, nil
}

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) []byte {
 length := len(origData)
 unpadding := int(origData[length-1])
 return origData[:(length - unpadding)]
}

再赠送两种MD5加密写法,也是利用了crypto库:

func TestMd5Create(t *testing.T) {
 CreateMd5Demo1("anyanfei")
 s := CreateMd5Demo2("anyanfei"// ca43e4338149bad344b75378ce5447ea
 fmt.Printf("%x\n", s)
}

func CreateMd5Demo1(s string){
 h := md5.New()
 io.WriteString(h, s)
 fmt.Printf("%x\n", h.Sum(nil)) // ca43e4338149bad344b75378ce5447ea
}

func CreateMd5Demo2(s string) [16]byte {
 data := []byte(s)
 return md5.Sum(data)
}

总结

其实大部分加密方法都是前人栽树后人乘凉,我们只需要知道最简单的使用即可,至于里面的逻辑,真想了解可以自行看源码解决,本次只写了两个案例,也是工作中用到最多的AES对称加密

AES/ECB/PKCS5Padding  ECB不需要带偏移量的对称加密写法

AES/CBC/PKCS5Padding  带偏移量的对称加密写法

MD5加密写法

参考资料

  • https://godoc.org/golang.org/x/crypto

还想了解更多吗?

更多请查看:https://godoc.org/golang.org/x/crypto

欢迎加入我们GOLANG中国社区:https://gocn.vip/


《酷Go推荐》招募:


各位Gopher同学,最近我们社区打算推出一个类似GoCN每日新闻的新栏目《酷Go推荐》,主要是每周推荐一个库或者好的项目,然后写一点这个库使用方法或者优点之类的,这样可以真正的帮助到大家能够学习到

新的库,并且知道怎么用。


大概规则和每日新闻类似,如果报名人多的话每个人一个月轮到一次,欢迎大家报名!戳「阅读原文」,即可报名


扫码也可以加入 GoCN 的大家族哟~





浏览 19
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报