我一直有一些问题试图找出为什么我的加密与php和节点相比有所不同。我希望有人可以帮助我找出差异。我们假设这是数据:
明文:hello big worldshello big worlds
键:jJr44P3WSM5F8AC573racFpzU5zj7Rg5
iv:97iEhhtgVjoVwdUw
以下是base64中的结果加密:
节点和PHP返回:
OTdpRWhodGdWam9Wd2RVd0OgJ+Z7pSCVioYq41721jarxqLKXN3PcnnY6/AOrHeEfsTxXfCgm2uUi+vmCAdpvw==
Go返回:
OTdpRWhodGdWam9Wd2RVd0OgJ+Z7pSCVioYq41721jarxqLKXN3PcnnY6/AOrHeE
正如你所看到的那样,它们几乎完全一样,它让我疯狂。你们可以快速查看下面的加密代码,并给我一些关于问题可能的提示吗?
GO:
func EncryptString(plainstring string, keystring string, encFormat int, ivOverride bool) (string) {
// Load your secret key from a safe place and reuse it across multiple
// NewCipher calls. (Obviously don't use this example key for anything
// real.) If you want to convert a passphrase to a key, use a suitable
// package like bcrypt or scrypt.
key := []byte(keystring)
plaintext := []byte(plainstring)
// CBC mode works on blocks so plaintexts may need to be padded to the
// next whole block. For an example of such padding, see
// https://tools.ietf.org/html/rfc5246#section-6.2.3.2. Here we'll
// assume that the plaintext is already of the correct length.
if len(plaintext)%aes.BlockSize != 0 {
panic("plaintext is not a multiple of the block size")
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// The IV needs to be unique, but not secure. Therefore it's common to
// include it at the beginning of the ciphertext.
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(bytes.NewReader([]byte("97iEhhtgVjoVwdUw")), iv); err != nil {
panic(err)
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
// It's important to remember that ciphertexts must be authenticated
// (i.e. by using crypto/hmac) as well as being encrypted in order to
// be secure.
return base64.StdEncoding.EncodeToString(ciphertext)
}
NODE:
encryptString: function(string, key, fmt = null, ivOverride = false) {
// Build an initialisation vector
let iv;
if(!ivOverride) {
iv = crypto.randomBytes(IV_NUM_BYTES).toString('hex').slice(0,16);
} else {
iv = IV_OVERRIDE_VALUE; //97iEhhtgVjoVwdUw
}
// and encrypt
let encryptor = crypto.createCipheriv('aes-256-cbc', key, iv);
let encryptedData = encryptor.update(string, 'utf8', 'binary') + encryptor.final('binary');
encryptedData = iv+''+encryptedData;
encryptedData = Buffer.from(encryptedData, 'binary').toString('base64');
return encryptedData;
}
我注意到删除encryptor.final('binary')
会导致两个加密相同但php没有.final()的东西。似乎有内置的Php uses open_ssl_encrypt()
。有没有办法在go中添加等价物?寻求建议。感谢
答案 0 :(得分:2)
好吧,我设法让go输出与你的其他输出相匹配,但我并不是100%清楚所有细节 - 特别是为什么PHP和Node版本的行为与他们的行为相同(你的Go版本的输出看起来像是"正确的"结果在我脑海中,正如我将要解释的那样。
我的第一个观察结果是Node和PHP的输出比Go版本长了大约一个块长度,只有尾端不同。这告诉我,不知何故,这些版本被填充 more 而不是go版本。
所以,我尝试根据PHP和Node PKCS#7使用的默认填充方案填充Go版本。基本上,如果你需要填充5个字节,那么每个填充字节应该等于0x05,6个字节用0x06填充,等等。默认aes.BlockSize
等于16,所以我尝试用16 0x10字节填充输入字符串。这导致了正确答案!
老实说,如果它已经是块对齐的,那么根本不填充输入是可以理解的行为,但显然Node和PHP遵循RFC 5652并且总是添加填充(参见编辑),即使他们需要添加另一个整个块只是填充。
这里是使输出匹配的Go代码:
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"io"
)
// Based on Wikipedia: https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7
func PadToBlockSize(input string) string {
paddingNeeded := aes.BlockSize - (len(input) % aes.BlockSize)
if paddingNeeded >= 256 {
panic("I'm too lazy to handle this case for the sake of an example :)")
}
if paddingNeeded == 0 {
paddingNeeded = aes.BlockSize
}
// Inefficient, once again, this is an example only!
for i := 0; i < paddingNeeded; i++ {
input += string(byte(paddingNeeded))
}
return input
}
// (Identical to your code, I just deleted comments to save space)
func EncryptString(plainstring string, keystring string, encFormat int, ivOverride bool) string {
key := []byte(keystring)
plaintext := []byte(plainstring)
if len(plaintext)%aes.BlockSize != 0 {
panic("plaintext is not a multiple of the block size")
}
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(bytes.NewReader([]byte("97iEhhtgVjoVwdUw")), iv); err != nil {
panic(err)
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
return base64.StdEncoding.EncodeToString(ciphertext)
}
func main() {
plaintext := "hello big worldshello big worlds"
key := "jJr44P3WSM5F8AC573racFpzU5zj7Rg5"
phpText := "OTdpRWhodGdWam9Wd2RVd0OgJ+Z7pSCVioYq41721jarxqLKXN3PcnnY6/AOrHeEfsTxXfCgm2uUi+vmCAdpvw=="
fmt.Println("Go : " + EncryptString(PadToBlockSize(plaintext), key, 0, false))
fmt.Println("PHP: " + phpText)
}
编辑:
实际上,看起来Node和PHP正好跟随RFC 5652,这要求所有输入都需要填充。即使输入是块对齐的,填充将始终存在的事实消除了解密的歧义。只需将填充步骤留给用户。