由于之前的程序是laravel写的,加解密是其特有的,现在整个程序用go重构,所以需要支持原来的加解密方式,经过测试勉强能用
我用的laravel的版本:7.25.0
先贴原php代码:
<?php
namespace Illuminate\Encryption;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
use Illuminate\Contracts\Encryption\EncryptException;
use RuntimeException;
class Encrypter implements EncrypterContract
{
/**
* The encryption key.
*
* @var string
*/
protected $key;
/**
* The algorithm used for encryption.
*
* @var string
*/
protected $cipher;
/**
* Create a new encrypter instance.
*
* @param string $key
* @param string $cipher
* @return void
*
* @throws \RuntimeException
*/
public function __construct($key, $cipher = 'AES-128-CBC')
{
$key = (string) $key;
if (static::supported($key, $cipher)) {
$this->key = $key;
$this->cipher = $cipher;
} else {
throw new RuntimeException('The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key lengths.');
}
}
/**
* Determine if the given key and cipher combination is valid.
*
* @param string $key
* @param string $cipher
* @return bool
*/
public static function supported($key, $cipher)
{
$length = mb_strlen($key, '8bit');
return ($cipher === 'AES-128-CBC' && $length === 16) ||
($cipher === 'AES-256-CBC' && $length === 32);
}
/**
* Create a new encryption key for the given cipher.
*
* @param string $cipher
* @return string
*/
public static function generateKey($cipher)
{
return random_bytes($cipher === 'AES-128-CBC' ? 16 : 32);
}
/**
* Encrypt the given value.
*
* @param mixed $value
* @param bool $serialize
* @return string
*
* @throws \Illuminate\Contracts\Encryption\EncryptException
*/
public function encrypt($value, $serialize = true)
{
$iv = random_bytes(openssl_cipher_iv_length($this->cipher));
// First we will encrypt the value using OpenSSL. After this is encrypted we
// will proceed to calculating a MAC for the encrypted value so that this
// value can be verified later as not having been changed by the users.
$value = \openssl_encrypt(
$serialize ? serialize($value) : $value,
$this->cipher, $this->key, 0, $iv
);
if ($value === false) {
throw new EncryptException('Could not encrypt the data.');
}
// Once we get the encrypted value we'll go ahead and base64_encode the input
// vector and create the MAC for the encrypted value so we can then verify
// its authenticity. Then, we'll JSON the data into the "payload" array.
$mac = $this->hash($iv = base64_encode($iv), $value);
$json = json_encode(compact('iv', 'value', 'mac'), JSON_UNESCAPED_SLASHES);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new EncryptException('Could not encrypt the data.');
}
return base64_encode($json);
}
/**
* Encrypt a string without serialization.
*
* @param string $value
* @return string
*
* @throws \Illuminate\Contracts\Encryption\EncryptException
*/
public function encryptString($value)
{
return $this->encrypt($value, false);
}
/**
* Decrypt the given value.
*
* @param string $payload
* @param bool $unserialize
* @return mixed
*
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
public function decrypt($payload, $unserialize = true)
{
$payload = $this->getJsonPayload($payload);
$iv = base64_decode($payload['iv']);
// Here we will decrypt the value. If we are able to successfully decrypt it
// we will then unserialize it and return it out to the caller. If we are
// unable to decrypt this value we will throw out an exception message.
$decrypted = \openssl_decrypt(
$payload['value'], $this->cipher, $this->key, 0, $iv
);
if ($decrypted === false) {
throw new DecryptException('Could not decrypt the data.');
}
return $unserialize ? unserialize($decrypted) : $decrypted;
}
/**
* Decrypt the given string without unserialization.
*
* @param string $payload
* @return string
*
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
public function decryptString($payload)
{
return $this->decrypt($payload, false);
}
/**
* Create a MAC for the given value.
*
* @param string $iv
* @param mixed $value
* @return string
*/
protected function hash($iv, $value)
{
return hash_hmac('sha256', $iv.$value, $this->key);
}
/**
* Get the JSON array from the given payload.
*
* @param string $payload
* @return array
*
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
protected function getJsonPayload($payload)
{
$payload = json_decode(base64_decode($payload), true);
// If the payload is not valid JSON or does not have the proper keys set we will
// assume it is invalid and bail out of the routine since we will not be able
// to decrypt the given value. We'll also check the MAC for this encryption.
if (! $this->validPayload($payload)) {
throw new DecryptException('The payload is invalid.');
}
if (! $this->validMac($payload)) {
throw new DecryptException('The MAC is invalid.');
}
return $payload;
}
/**
* Verify that the encryption payload is valid.
*
* @param mixed $payload
* @return bool
*/
protected function validPayload($payload)
{
return is_array($payload) && isset($payload['iv'], $payload['value'], $payload['mac']) &&
strlen(base64_decode($payload['iv'], true)) === openssl_cipher_iv_length($this->cipher);
}
/**
* Determine if the MAC for the given payload is valid.
*
* @param array $payload
* @return bool
*/
protected function validMac(array $payload)
{
$calculated = $this->calculateMac($payload, $bytes = random_bytes(16));
return hash_equals(
hash_hmac('sha256', $payload['mac'], $bytes, true), $calculated
);
}
/**
* Calculate the hash of the given payload.
*
* @param array $payload
* @param string $bytes
* @return string
*/
protected function calculateMac($payload, $bytes)
{
return hash_hmac(
'sha256', $this->hash($payload['iv'], $payload['value']), $bytes, true
);
}
/**
* Get the encryption key.
*
* @return string
*/
public function getKey()
{
return $this->key;
}
}
laravel默认是AES-128-CBC加密,但是config配置是256,所以我go那边就只考虑256
'cipher' => 'AES-256-CBC',
现贴go翻译版本:
// trans from laravel Encrypter
package encrypter
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"github.com/techoner/gophp"
)
const DEFAULT_CIPHER = "AES-256-CBC"
type Encrypter struct {
Key []byte
}
//key len=32
func New(key []byte) *Encrypter {
return &Encrypter{Key: key}
}
func (e *Encrypter) Encrypt(text string) (string, error) {
origData, err := gophp.Serialize(text)
if err != nil {
return "", err
}
ivLength, err := getIvLengthOfCipher(DEFAULT_CIPHER)
if err != nil {
return "", err
}
iv, err := randomIvBytes(ivLength)
if err != nil {
return "", err
}
block, err := aes.NewCipher(e.Key)
if err != nil {
return "", err
}
mode := cipher.NewCBCEncrypter(block, iv)
blockSize := block.BlockSize() // 获取秘钥块的长度
origData = pkcs5Padding(origData, blockSize)
encryptedByCbc := make([]byte, len(origData))
mode.CryptBlocks(encryptedByCbc, origData) //注意php那边cbc自动base64,这边要自己处理
base64Iv := base64.StdEncoding.EncodeToString(iv)
mac := e.hash(base64Iv + string(base64.StdEncoding.EncodeToString(encryptedByCbc)))
jsonData, err := json.Marshal(&payload{Iv: string(base64Iv), Value: string(base64.StdEncoding.EncodeToString(encryptedByCbc)), Mac: mac})
if err != nil {
return "", err
}
//fmt.Println(string(jsonData))
return base64.StdEncoding.EncodeToString(jsonData), nil
}
//hmacSha256
func (e *Encrypter) hash(data string) string {
h := hmac.New(sha256.New, e.Key)
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func (e *Encrypter) Decrypt(text string) (string, error) {
pl, err := getJsonPayload(text)
//fmt.Println(pl, err)
if err != nil {
return "", err
}
iv, err := base64.StdEncoding.DecodeString(pl.Iv)
if err != nil {
return "", err
}
if err != nil {
return "", err
}
block, err := aes.NewCipher(e.Key)
if err != nil {
return "", err
}
mode := cipher.NewCBCDecrypter(block, iv)
//blockSize := block.BlockSize()
encrypted, err := base64.StdEncoding.DecodeString(pl.Value)
if err != nil {
return "", err
}
decrypted := make([]byte, len(encrypted)) // 创建数组
mode.CryptBlocks(decrypted, encrypted) // 解密
decrypted = pkcs5UnPadding(decrypted) // 去除补全码
out, err := gophp.Unserialize(decrypted)
if err != nil {
return "", errors.New("Fail to unserialize data")
}
return fmt.Sprintf("%s", out), nil
}
type payload struct {
Iv string `json:"iv"`
Value string `json:"value"`
Mac string `json:"mac"`
}
func getJsonPayload(text string) (*payload, error) {
text = strings.Replace(text, " ", "", -1) //base64.StdEncoding.DecodeString:illegal base64 data at input byte: one reason is whitespace
data, err := base64.StdEncoding.DecodeString(text)
//fmt.Println(string(data), err)
if err != nil {
return nil, err
}
pl := new(payload)
err = json.Unmarshal(data, pl)
if err != nil {
return nil, err
}
//todo check hash(mac)
return pl, err
}
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)]
}
func getIvLengthOfCipher(cipher string) (int, error) {
switch cipher {
case "AES-256-CBC":
return aes.BlockSize, nil // 16
}
return 0, errors.New("The cipher method not support")
}
func randomIvBytes(ivLength int) ([]byte, error) {
iv := make([]byte, ivLength)
_, err := io.ReadFull(rand.Reader, iv)
return iv, err
}
go这边解密我就没去验证mac,需要验证自己加上就行。
|