【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
還想了解更多嗎?
更多請查看:https://godoc.org/golang.org/x/crypto
歡迎加入我們 GOLANG 中國社區:https://gocn.vip/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/oYHPdLay6cGMJiPTptTviw