golang java AES 加密 SHA1PRNG

前言

和不同语言的第三方对接不可避免的遇到语言不一致的问题,比如加解密,有关于 golang 的 CBC/ECB/CFB 加密方法,这篇博客写的很全 传送门->

本文主要介绍 java AES 中出现的随机算法 SHA1PRNG 生成key,在 golang 中的实现方法

描述

先来看 java 代码

// java 代码
// content:test123
// encryptKey:123456
// 加密结果为:668C826342B8703D86E8BBF404610499
public static byte[] encrypt(String content, String encryptKey) {
    try {
      KeyGenerator kgen = KeyGenerator.getInstance("AES");
      SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
      secureRandom.setSeed(encryptKey.getBytes());

      kgen.init(128, secureRandom);
      SecretKey secretKey = kgen.generateKey();
      byte[] enCodeFormat = secretKey.getEncoded();
      SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
      Cipher cipher = Cipher.getInstance("AES"); // AES 和 AES/ECB/PKCS5Padding 等价
      byte[] byteContent = content.getBytes("utf-8");
      cipher.init(1, key);
      byte[] result = cipher.doFinal(byteContent);
      return result;
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } catch (NoSuchPaddingException e) {
      e.printStackTrace();
    } catch (InvalidKeyException e) {
      e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
      e.printStackTrace();
    } catch (BadPaddingException e) {
      e.printStackTrace();
    }
    return null;
  }

然后来看 golang 代码,示例1:

// go 代码
// content:test123
// encryptKey:123456
// 加密结果为:A794E6B031BFA05023B8C4577CA60595
// 如果直接用下面这个标准方法加密,得出来的结果和 java 中是不一致的,是因为对 key 缺少类似java SHA1PRNG的处理
func AesEncryptECB(origData []byte, key []byte) (encrypted []byte) {
	cipher, _ := aes.NewCipher(generateKey(key))
	length := (len(origData) + aes.BlockSize) / aes.BlockSize
	plain := make([]byte, length*aes.BlockSize)
	copy(plain, origData)
	pad := byte(len(plain) - len(origData))
	for i := len(origData); i < len(plain); i++ {
		plain[i] = pad
	}
	encrypted = make([]byte, len(plain))
	// 分组分块加密
	for bs, be := 0, cipher.BlockSize(); bs <= len(origData); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
		cipher.Encrypt(encrypted[bs:be], plain[bs:be])
	}

	return encrypted
}

示例2:

// content:test123
// encryptKey:123456
// 加密结果为:668C826342B8703D86E8BBF404610499
// 此时就和 java 结果相对应了,解密也一样对 key 加一步处理就行
func AesEncryptECB(origData []byte, key []byte) (encrypted []byte) {
	key, _ = AesSha1prng(key, 128) // 比示例一多出这一步

	cipher, _ := aes.NewCipher(generateKey(key))
	length := (len(origData) + aes.BlockSize) / aes.BlockSize
	plain := make([]byte, length*aes.BlockSize)
	copy(plain, origData)
	pad := byte(len(plain) - len(origData))
	for i := len(origData); i < len(plain); i++ {
		plain[i] = pad
	}
	encrypted = make([]byte, len(plain))
	// 分组分块加密
	for bs, be := 0, cipher.BlockSize(); bs <= len(origData); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
		cipher.Encrypt(encrypted[bs:be], plain[bs:be])
	}

	return encrypted
}

// 模拟 java SHA1PRNG 处理
func AesSha1prng(keyBytes []byte, encryptLength int) ([]byte, error) {
	hashs := Sha1(Sha1(keyBytes))
	maxLen := len(hashs)
	realLen := encryptLength / 8
	if realLen > maxLen {
		return nil, errors.New("invalid length!")
	}

	return hashs[0:realLen], nil
}

func Sha1(data []byte) []byte {
	h := sha1.New()
	h.Write(data)
	return h.Sum(nil)
}

func generateKey(key []byte) (genKey []byte) {
	genKey = make([]byte, 16)
	copy(genKey, key)
	for i := 16; i < len(key); {
		for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
			genKey[j] ^= key[i]
		}
	}
	return genKey
}

加解密完整demo(2021-08-26更新)

有同学说示例不完整,在此补充一下吧 ~~

package test

import (
	"crypto/aes"
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"testing"
)

func Test_Crypto(t *testing.T) {
	str := []byte("test123")
	key := []byte("123456")
	// 加密
	res, err := AesEncryptECB(str, key)
	if err != nil {
		t.Fatal(err)
	}
	t.Log(hex.EncodeToString(res)) // 输出:668c826342b8703d86e8bbf404610499

	// 解密
	de, err := AesDecryptECB(res, key)
	if err != nil {
		t.Fatal(err)
	}
	t.Log(string(de))
}

// content:test123
// encryptKey:123456
// 加密结果为:668C826342B8703D86E8BBF404610499
// 此时就和 java 结果相对应了,解密也一样对 key 加一步处理就行
func AesEncryptECB(src []byte, key []byte) ([]byte, error) {
	key, err := AesSha1prng(key, 128) // 比示例一多出这一步
	if err != nil {
		return nil, err
	}

	cipher, _ := aes.NewCipher(generateKey(key))
	length := (len(src) + aes.BlockSize) / aes.BlockSize
	plain := make([]byte, length*aes.BlockSize)
	copy(plain, src)
	pad := byte(len(plain) - len(src))
	for i := len(src); i < len(plain); i++ {
		plain[i] = pad
	}
	encrypted := make([]byte, len(plain))
	// 分组分块加密
	for bs, be := 0, cipher.BlockSize(); bs <= len(src); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
		cipher.Encrypt(encrypted[bs:be], plain[bs:be])
	}

	return encrypted, nil
}

func AesDecryptECB(encrypted []byte, key []byte) ([]byte, error) {
	key, err := AesSha1prng(key, 128) // 比示例一多出这一步
	if err != nil {
		return nil, err
	}

	cipher, _ := aes.NewCipher(generateKey(key))
	decrypted := make([]byte, len(encrypted))
	//
	for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
		cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
	}

	trim := 0
	if len(decrypted) > 0 {
		trim = len(decrypted) - int(decrypted[len(decrypted)-1])
	}

	return decrypted[:trim], nil
}

// 模拟 java SHA1PRNG 处理
func AesSha1prng(keyBytes []byte, encryptLength int) ([]byte, error) {
	hashs := Sha1(Sha1(keyBytes))
	maxLen := len(hashs)
	realLen := encryptLength / 8
	if realLen > maxLen {
		return nil, errors.New("invalid length!")
	}

	return hashs[0:realLen], nil
}

func Sha1(data []byte) []byte {
	h := sha1.New()
	h.Write(data)
	return h.Sum(nil)
}

func generateKey(key []byte) (genKey []byte) {
	genKey = make([]byte, 16)
	copy(genKey, key)
	for i := 16; i < len(key); {
		for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
			genKey[j] ^= key[i]
		}
	}
	return genKey
}

总结

总的来说两者差不多,但是直接使用 golang 中的 AES ECB来进行加解密是有问题的,这点从在线加解密网站也可以看出来,对于 java 中进行了 SHA1PRNG 处理数据是结果是对应不上的!

参考博文:golang:实现Java 中的 AES SHA1PRNG算法_OceanStar的博客-CSDN博客

感谢您的阅读,欢迎点赞、关注、收藏!如果文章中有任何错误,或者您有更好的理解和建议,也欢迎和我联系!

下面是一个使用golang中的crypto库进行AES GCM模式加密文件的示例代码: ```go package main import ( "crypto/aes" "crypto/cipher" "crypto/rand" "fmt" "io" "io/ioutil" "os" ) func main() { // 读取原始文件 data, err := ioutil.ReadFile("plaintext.txt") if err != nil { panic(err) } // 随机生成16字节的密钥 key := make([]byte, 16) if _, err := io.ReadFull(rand.Reader, key); err != nil { panic(err) } // 创建GCM加密器 block, err := aes.NewCipher(key) if err != nil { panic(err) } aesgcm, err := cipher.NewGCM(block) if err != nil { panic(err) } // 随机生成12字节的IV iv := make([]byte, 12) if _, err := io.ReadFull(rand.Reader, iv); err != nil { panic(err) } // 使用GCM加密数据 ciphertext := aesgcm.Seal(nil, iv, data, nil) // 将密钥和IV写入文件开头 output := append(key, iv...) // 将加密数据写入文件尾部 output = append(output, ciphertext...) // 将加密结果写入文件 err = ioutil.WriteFile("ciphertext.bin", output, 0644) if err != nil { panic(err) } fmt.Println("Encryption complete.") } ``` 这个示例代码中,我们首先读取要加密的文件的内容。然后,我们使用crypto库中提供的`aes.NewCipher`函数创建一个AES加密器,并使用这个加密器创建一个GCM加密器。接下来,我们随机生成一个16字节的密钥和一个12字节的IV,然后使用GCM加密器对原始数据进行加密。最后,我们将密钥和IV写入加密文件的开头,将加密数据写入文件的尾部。 注意,在实际应用中,我们应该使用更加安全的方式来生成密钥和IV,例如使用密码学安全的伪随机数生成器。此外,我们也应该对加密文件进行适当的保护,例如使用密码或者其他的访问控制措施。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值