问题描述
我在 Ruby 中有加密和解密功能,并尝试用 Go 重写。我一步一步尝试,所以从 ruby 加密开始,然后尝试在 go 中解密,它的工作原理。但是当我尝试在 Go 中编写 encryption 并在 ruby 中解密时。我在尝试提取标签时卡住了,我解释了为什么我需要提取 auth 标签
ruby 中的加密
plaintext = "Foo bar"
cipher = OpenSSL::Cipher.new('aes-256-gcm')
cipher.encrypt
cipher.iv = iv # string 12 len
cipher.key = key # string 32 len
cipher.auth_data = # string 12 len
cipherText = cipher.update(JSON.generate({value: plaintext})) + cipher.final
authTag = cipher.auth_tag
hexString = (iv + cipherText + authTag).unpack('H*').first
我尝试连接初始向量、密文和认证标签,所以在解密之前我可以提取它们,尤其是认证标签,因为我需要在 Ruby 中调用 Cipher#final 之前设置它
标签必须在调用 Cipher#decrypt、Cipher#key= 和 Cipher#iv=,但在调用 Cipher#final 之前。毕竟解密是 执行,标签在调用中自动验证 密码#final
这里是golang中的函数加密
ciphertext := aesgcm.Seal(nil,[]byte(iv),[]byte(plaintext),[]byte(authData))
src := iv + string(ciphertext) // + try to add authentication tag here
fmt.Printf(hex.EncodetoString([]byte(src)))
我如何提取认证标签并将其与iv和密文连接起来,这样我就可以用ruby中的解密函数解密
raw_data = [hexString].pack('H*')
cipher_text = raw_data.slice(12,raw_data.length - 28)
auth_tag = raw_data.slice(raw_data.length - 16,16)
cipher = OpenSSL::Cipher.new('aes-256-gcm').decrypt
cipher.iv = iv # string 12 len
cipher.key = key # string 32 len
cipher.auth_data = # string 12 len
cipher.auth_tag = auth_tag
JSON.parse(cipher.update(cipher_text) + cipher.final)
我希望能够在 Go 中进行加密,并尝试在 Ruby 中解密。
解决方法
您希望您的加密流程是这样的:
func encrypt(in []byte,key []byte) (out []byte,err error) {
c,err := aes.NewCipher(key)
if err != nil {
return
}
gcm,err := cipher.NewGCM(c)
if err != nil {
return
}
nonce := make([]byte,gcm.NonceSize())
if _,err = io.ReadFull(rand.Reader,nonce); err != nil {
return
}
out = gcm.Seal(nonce,nonce,in,nil) // include the nonce in the preable of 'out'
return
}
根据 aes.NewCipher 文档,您的输入 key
的长度应为 16、24 或 32 个字节。
来自上述函数的加密 out
字节将包含 nonce
前缀(长度为 16、24 或 32 个字节)-因此可以在解密阶段轻松提取,如下所示:>
// `in` here is ciphertext
nonce,ciphertext := in[:ns],in[ns:]
其中 ns
的计算方式如下:
c,err := aes.NewCipher(key)
if err != nil {
return
}
gcm,err := cipher.NewGCM(c)
if err != nil {
return
}
ns := gcm.NonceSize()
if len(in) < ns {
err = fmt.Errorf("missing nonce - input shorter than %d bytes",ns)
return
}
编辑:
如果您使用默认密码设置(见上文)在 go
端加密:
gcm,err := cipher.NewGCM(c)
从 the source 开始,标签字节大小将为 16
。
注意:如果使用 cipher.NewGCMWithTagSize - 那么大小显然会不同(基本上在 12
到 16
字节之间)
所以让我们假设标签大小是 16
,掌握了这些知识,并且知道完整的有效载荷安排是:
IV/nonce + raw_ciphertext + auth_tag
用于解密的auth_tag
端的Ruby
,是payload的最后16个字节;而 raw_ciphertext 是 IV/nonce
之后直到 auth_tag
开始的所有字节。
aesgcm.Seal
自动在密文末尾附加 GCM 标签。您可以在 source:
var tag [gcmTagSize]byte
g.auth(tag[:],out[:len(plaintext)],data,&tagMask)
copy(out[len(plaintext):],tag[:]) // <---------------- here
所以你已经完成了,你不需要任何其他东西。 gcm.Seal
已经返回密文,并在末尾附加了 auth 标签。
同样,您不需要提取 gcm.Open
的 auth 标签,它会自动提取,too:
tag := ciphertext[len(ciphertext)-g.tagSize:] // <---------------- here
ciphertext = ciphertext[:len(ciphertext)-g.tagSize]
因此,您在解密过程中要做的就是提取 IV(随机数)并将其余部分作为密文传递。