【Go 推薦】crypto 官方加解密庫

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([]byte, len(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([]byte, len(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([]byte, len(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([]byte, len(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

歡迎加入我們 GOLANG 中國社區:https://gocn.vip/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/oYHPdLay6cGMJiPTptTviw