加密方式及解密算法
加密算法
EncryptKey即在界面填写的Secret值。
加密过程:
1、使用SHA256对EncryptKey进行哈希得到密钥key;
2、生成16个字节的随机数作为初始向量iv;
3、使用iv和key对事件内容加密得到encryped_event;(事件内容采用AES-256-CBC加密)
4、应用收到的密文encrypt为base64(iv+encryped_event)。
根据加密算法,自行进行解密,可参考解密算法示例。
解密算法
以下提供了PHP、Java、Golang、Python、C#、Node.js等这几种语言的解密算法供开发者参考。
PHP
<?php
function decrypt($key, $input)
{
$key = hash('sha256', $key, true);
$input_bin = base64_decode($input);
$data = substr($input_bin, 16);
$iv = substr($input_bin, 0, 16);
$output = openssl_decrypt($data, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
return $output;
}
$res = decrypt("test key", "P37w+VZImNgPEO1RBhJ6RtKl7n6zymIbEG1pReEzghk=");
var_dump($res); // string(11) "hello world"
Java
package com.larksuite.oapi.sample;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class Decrypt {
public static void main(String[] args) throws Exception {
Decrypt d = new Decrypt("test key");
System.out.println(d.decrypt("P37w+VZImNgPEO1RBhJ6RtKl7n6zymIbEG1pReEzghk=")); //hello world
}
private byte[] keyBs;
public Decrypt(String key) {
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
// won't happen
}
keyBs = digest.digest(key.getBytes(StandardCharsets.UTF_8));
}
public String decrypt(String base64) throws Exception {
byte[] decode = Base64.getDecoder().decode(base64);
Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING");
byte[] iv = new byte[16];
System.arraycopy(decode, 0, iv, 0, 16);
byte[] data = new byte[decode.length - 16];
System.arraycopy(decode, 16, data, 0, data.length);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBs, "AES"), new IvParameterSpec(iv));
byte[] r = cipher.doFinal(data);
if (r.length > 0) {
int p = r.length - 1;
for (; p >= 0 && r[p] <= 16; p--) {
}
if (p != r.length - 1) {
byte[] rr = new byte[p + 1];
System.arraycopy(r, 0, rr, 0, p + 1);
r = rr;
}
}
return new String(r, StandardCharsets.UTF_8);
}
}
Golang
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"strings"
)
func main() {
s, err := Decrypt("P37w+VZImNgPEO1RBhJ6RtKl7n6zymIbEG1pReEzghk=", "test key")
if err != nil {
panic(err)
}
fmt.Println(s) //hello world
}
func Decrypt(encrypt string, key string) (string, error) {
buf, err := base64.StdEncoding.DecodeString(encrypt)
if err != nil {
return "", fmt.Errorf("base64StdEncode Error[%v]", err)
}
if len(buf) < aes.BlockSize {
return "", errors.New("cipher too short")
}
keyBs := sha256.Sum256([]byte(key))
block, err := aes.NewCipher(keyBs[:sha256.Size])
if err != nil {
return "", fmt.Errorf("AESNewCipher Error[%v]", err)
}
iv := buf[:aes.BlockSize]
buf = buf[aes.BlockSize:]
// CBC mode always works in whole blocks.
if len(buf)%aes.BlockSize != 0 {
return "", errors.New("ciphertext is not a multiple of the block size")
}
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(buf, buf)
n := strings.Index(string(buf), "{")
if n == -1 {
n = 0
}
m := strings.LastIndex(string(buf), "}")
if m == -1 {
m = len(buf) - 1
}
return string(buf[n : m+1]), nil
}
Python
import hashlib
import base64
from Crypto.Cipher import AES
class AESCipher(object):
def __init__(self, key):
self.bs = AES.block_size
self.key=hashlib.sha256(AESCipher.str_to_bytes(key)).digest()
@staticmethod
def str_to_bytes(data):
u_type = type(b"".decode('utf8'))
if isinstance(data, u_type):
return data.encode('utf8')
return data
@staticmethod
def _unpad(s):
return s[:-ord(s[len(s) - 1:])]
def decrypt(self, enc):
iv = enc[:AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return self._unpad(cipher.decrypt(enc[AES.block_size:]))
def decrypt_string(self, enc):
enc = base64.b64decode(enc)
return self.decrypt(enc).decode('utf8')
encrypt = "P37w+VZImNgPEO1RBhJ6RtKl7n6zymIbEG1pReEzghk="
cipher = AESCipher("test key")
print("明文:\n{}".format(cipher.decrypt_string(encrypt)))
# 明文:hello world
C
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace decrypt
{
class AESCipher
{
const int BlockSize = 16;
private byte[] key;
public AESCipher(string key)
{
this.key = SHA256Hash(key);
}
public string DecryptString(string enc)
{
byte[] encBytes = Convert.FromBase64String(enc);
RijndaelManaged rijndaelManaged = new RijndaelManaged();
rijndaelManaged.Key = this.key;
rijndaelManaged.Mode = CipherMode.CBC;
rijndaelManaged.IV = encBytes.Take(BlockSize).ToArray();
ICryptoTransform transform = rijndaelManaged.CreateDecryptor();
byte[] blockBytes = transform.TransformFinalBlock(encBytes, BlockSize, encBytes.Length - BlockSize);
return System.Text.Encoding.UTF8.GetString(blockBytes);
}
public static byte[] SHA256Hash(string str)
{
byte[] bytes = Encoding.UTF8.GetBytes(str);
SHA256 shaManaged = new SHA256Managed();
return shaManaged.ComputeHash(bytes);
}
public static void Main(string[] args)
{
string encrypt = "P37w+VZImNgPEO1RBhJ6RtKl7n6zymIbEG1pReEzghk=";
AESCipher cipher = new AESCipher("test key");
Console.WriteLine(cipher.DecryptString(encrypt));
}
}
}
Node.js
const crypto = require("crypto");
class AESCipher {
constructor(key) {
const hash = crypto.createHash('sha256');
hash.update(key);
this.key = hash.digest();
}
decrypt(encrypt) {
const encryptBuffer = Buffer.from(encrypt, 'base64');
const decipher = crypto.createDecipheriv('aes-256-cbc', this.key, encryptBuffer.slice(0, 16));
let decrypted = decipher.update(encryptBuffer.slice(16).toString('hex'), 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}
encrypt = "P37w+VZImNgPEO1RBhJ6RtKl7n6zymIbEG1pReEzghk="
cipher = new AESCipher("test key")
console.log(cipher.decrypt(encrypt))
// hello world