如何将C#中的rijndaelManaged函数重写为Go语言?

huangapple go评论89阅读模式
英文:

How to rewrite rijndaelManaged function from C# to Go?

问题

可能只是我犯了一个我看不到的小错误。也许其他人在查看时可以找出我做错了什么。

这是我试图在Go中重写的C#函数,目标是在调用函数时输出相同的值。

public static string NewEncrypt(string Input)
{
    RijndaelManaged rijndaelManaged = new RijndaelManaged();
    rijndaelManaged.KeySize = 256;
    rijndaelManaged.BlockSize = 256;
    rijndaelManaged.Padding = PaddingMode.PKCS7;
    rijndaelManaged.Key = Convert.FromBase64String(Convert.ToBase64String(Encoding.UTF8.GetBytes("095fc90fe8b18e8f243e4b07a9c0d170")));
    rijndaelManaged.IV = Convert.FromBase64String(Convert.ToBase64String(Encoding.UTF8.GetBytes("8bef55a546d27958ead1fdddba4d36ea")));
    ICryptoTransform transform = rijndaelManaged.CreateEncryptor(rijndaelManaged.Key, rijndaelManaged.IV);
    byte[] myArray = null;
    using (MemoryStream memoryStream = new MemoryStream())
    {
        using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
        {
            byte[] bytes = Encoding.UTF8.GetBytes(Input);
            cryptoStream.Write(bytes, 0, bytes.Length);
        }
        myArray = memoryStream.ToArray();
    }
    return Convert.ToBase64String(myArray);
}

你可以这样调用它:

NewEncrypt("{\"randJsonList\":[\"abc\"], \"name\":\"baron\", \"support\":\"king\"}")

我们得到的返回输出(myArray)是:

DdSUyoYRYW/zDNSVaA1JZ39WqJt06qp0FiJUlCW5BbZWEt41GzsmtgVnGZuHigZNs7qKhI+kHAKMXL8EPnK1vg==

现在是我的Go实现(我试图利用GitHub资源:https://gist.github.com/huyinghuan/7bf174017bf54efb91ece04a48589b22):

你可能注意到的第一件事是,我不知道在哪里可以使用全局IV变量,每次运行此代码时,都会得到不同的输出。我希望输出与C#中的结果相同,除非输入字符串被修改。

package main

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
)

var KEY = []byte("095fc90fe8b18e8f243e4b07a9c0d170")
var IV = []byte("8bef55a546d27958ead1fdddba4d36ea")

func encrypt(myInput string) []byte {
	plaintext := []byte(myInput)

	// Create the AES cipher
	block, err := aes.NewCipher(KEY)
	if err != nil {
		panic(err)
	}

	plaintext, _ = pkcs7Pad(plaintext, block.BlockSize())
	// 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(rand.Reader, iv); err != nil {
		panic(err)
	}
	bm := cipher.NewCBCEncrypter(block, iv)
	bm.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
	//stream := cipher.NewCFBEncrypter(block, iv)
	//stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)

	return ciphertext
}

// pkcs7Pad right-pads the given byte slice with 1 to n bytes, where
// n is the block size. The size of the result is x times n, where x
// is at least 1.
func pkcs7Pad(b []byte, blocksize int) ([]byte, error) {
	if blocksize <= 0 {
		return nil, errors.New("invalid blocksize")
	}
	if b == nil || len(b) == 0 {
		return nil, errors.New("invalid PKCS7 data (empty or not padded)")
	}
	n := blocksize - (len(b) % blocksize)
	pb := make([]byte, len(b)+n)
	copy(pb, b)
	copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
	return pb, nil
}

func main() {
	plainText := "{\"randJsonList\":[\"abc\"], \"name\":\"baron\", \"support\":\"king\"}"
	x := encrypt(plainText)

	outputString := base64.StdEncoding.EncodeToString(x)
	fmt.Println(outputString)
}

示例输出(与C#不同):

PS D:\Software\Git\repositories\tet> go run .\main.go
N+hm5TItq367eXAz+WbtKXhhhMAy4woEKSngTf6rGUt8GZce7LsUxaqNtheceGDZ2dK8Bx187x87NeRPC1UQ6lUokjy7t1MLU8NcCtjODCM=
PS D:\Software\Git\repositories\tet> go run .\main.go
OT/CngTVs2O4BR4czjvR3MLVPoKFH2dUtW8LsIDUgLXfikJrRKsvKGaf0JFe39Cwf1/00HP7mvmCure7+IO+vupzAtdLX6nTQt1KZGsNp4o=
PS D:\Software\Git\repositories\tet> go run .\main.go
yDRHxWTvjX4HnSW8jbao+0Mhf77zgRj9tKXA3MNtAoF1I3bRou5Sv4Ds+r0HRuiA7NkoBR57m4aCYcU6quYzQA3R0GCGB8TGUfrWS5PvMNU=
英文:

There might be just a minor mistake I have done that I cannot see. Perhaps someone else looking over this could figure out what I am doing wrong.

This is function in C# that I am trying to rewrite in Go, the objective is to output the same value when calling a function.

public static string NewEncrypt(string Input)
{
RijndaelManaged rijndaelManaged = new RijndaelManaged();
rijndaelManaged.KeySize = 256;
rijndaelManaged.BlockSize = 256;
rijndaelManaged.Padding = PaddingMode.PKCS7;
rijndaelManaged.Key = Convert.FromBase64String(Convert.ToBase64String(Encoding.UTF8.GetBytes(&quot;095fc90fe8b18e8f243e4b07a9c0d170&quot;)));
rijndaelManaged.IV = Convert.FromBase64String(Convert.ToBase64String(Encoding.UTF8.GetBytes(&quot;8bef55a546d27958ead1fdddba4d36ea&quot;)));
ICryptoTransform transform = rijndaelManaged.CreateEncryptor(rijndaelManaged.Key, rijndaelManaged.IV);
byte[] myArray = null;
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
{
byte[] bytes = Encoding.UTF8.GetBytes(Input);
cryptoStream.Write(bytes, 0, bytes.Length);
}
myArray = memoryStream.ToArray();
}
return Convert.ToBase64String(myArray);
}

You can call it using:

NewEncrypt(&quot;{\&quot;randJsonList\&quot;:[ \&quot;abc\&quot; ], \&quot;name\&quot;:\&quot;baron\&quot;, \&quot;support\&quot;:\&quot;king\&quot;}&quot;)

We have this return output (myArray):

DdSUyoYRYW/zDNSVaA1JZ39WqJt06qp0FiJUlCW5BbZWEt41GzsmtgVnGZuHigZNs7qKhI+kHAKMXL8EPnK1vg==

Now for my Go implementation (I was trying to make use of GitHub resources: https://gist.github.com/huyinghuan/7bf174017bf54efb91ece04a48589b22):

First thing you might notice is that I do not know where I can use global IV variable, each time you run this code you are presented with different output. I want to output the same result as in C#, unless the input string is modified

package main
import (
&quot;bytes&quot;
&quot;crypto/aes&quot;
&quot;crypto/cipher&quot;
&quot;crypto/rand&quot;
&quot;encoding/base64&quot;
&quot;errors&quot;
&quot;fmt&quot;
&quot;io&quot;
)
var KEY = []byte(&quot;095fc90fe8b18e8f243e4b07a9c0d170&quot;)
var IV = []byte(&quot;8bef55a546d27958ead1fdddba4d36ea&quot;)
func encrypt(myInput string) []byte {
plaintext := []byte(myInput)
// Create the AES cipher
block, err := aes.NewCipher(KEY)
if err != nil {
panic(err)
}
plaintext, _ = pkcs7Pad(plaintext, block.BlockSize())
// The IV needs to be unique, but not secure. Therefore it&#39;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(rand.Reader, iv); err != nil {
panic(err)
}
bm := cipher.NewCBCEncrypter(block, iv)
bm.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
//stream := cipher.NewCFBEncrypter(block, iv)
//stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
return ciphertext
}
// pkcs7Pad right-pads the given byte slice with 1 to n bytes, where
// n is the block size. The size of the result is x times n, where x
// is at least 1.
func pkcs7Pad(b []byte, blocksize int) ([]byte, error) {
if blocksize &lt;= 0 {
return nil, errors.New(&quot;invalid blocksize&quot;)
}
if b == nil || len(b) == 0 {
return nil, errors.New(&quot;invalid PKCS7 data (empty or not padded)&quot;)
}
n := blocksize - (len(b) % blocksize)
pb := make([]byte, len(b)+n)
copy(pb, b)
copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
return pb, nil
}
func main() {
plainText := &quot;{\&quot;randJsonList\&quot;:[ \&quot;abc\&quot; ], \&quot;name\&quot;:\&quot;baron\&quot;, \&quot;support\&quot;:\&quot;king\&quot;}&quot;
x := encrypt(plainText)
outputString := base64.StdEncoding.EncodeToString(x)
fmt.Println(outputString)
}

Example output (not the same as C#):

PS D:\Software\Git\repositories\tet&gt; go run .\main.go
N+hm5TItq367eXAz+WbtKXhhhMAy4woEKSngTf6rGUt8GZce7LsUxaqNtheceGDZ2dK8Bx187x87NeRPC1UQ6lUokjy7t1MLU8NcCtjODCM=
PS D:\Software\Git\repositories\tet&gt; go run .\main.go
OT/CngTVs2O4BR4czjvR3MLVPoKFH2dUtW8LsIDUgLXfikJrRKsvKGaf0JFe39Cwf1/00HP7mvmCure7+IO+vupzAtdLX6nTQt1KZGsNp4o=
PS D:\Software\Git\repositories\tet&gt; go run .\main.go
yDRHxWTvjX4HnSW8jbao+0Mhf77zgRj9tKXA3MNtAoF1I3bRou5Sv4Ds+r0HRuiA7NkoBR57m4aCYcU6quYzQA3R0GCGB8TGUfrWS5PvMNU=

答案1

得分: 4

C#代码使用了具有256位块大小的Rijndael(请参见注释),静态IV(请参见注释),并且仅返回密文(即不包含前置IV)。Go代码使用AES,其块大小定义为128位,使用随机生成的IV(代码中的静态IV被忽略),并返回IV和密文的连接。

AES是Rijndael的子集。Rijndael定义了不同的块大小和密钥大小,介于128位和256位之间,以32位为步长,详见这里。对于AES,仅定义了块大小为128位和密钥大小为128、192和256位。请注意,标准是AES而不是Rijndael,因此应优先选择AES(许多库甚至不实现Rijndael,而是实现AES)。

静态IV是不安全的。出于安全原因,密钥/IV对不得重复。因此,通常情况下,对于固定密钥,会为每个加密生成一个随机IV。IV不是秘密的,并且与密文一起传递到解密方,通常按顺序连接为IV|密文

因此,当前的Go代码是一个安全的实现(更安全的是通过GCM进行认证加密),而C#代码不是。因此,将C#代码修改为与Go代码在功能上等效更有意义。但是,由于C#代码似乎是参考代码,因此需要对Go代码进行以下更改,以使其在功能上与C#代码完全相同:

  • 替换AES为Rijndael。在下面的示例中,使用pkg.go.dev/github.com/azihsoyn/rijndael256。为此,请导入"github.com/azihsoyn/rijndael256",并正式将aes替换为rijndael256。当然,你也可以使用其他实现。
  • 应用静态IV:bm := cipher.NewCBCEncrypter(block, IV)
  • 应删除iv及其填充以及相关的导入。
  • enecrypt()方法中仅返回密文:return ciphertext[rijndael256.BlockSize:]

以下Go代码给出了与C#代码相同的结果:

package main

import (
    "bytes"
    "github.com/azihsoyn/rijndael256"
    "crypto/cipher"
    "encoding/base64"
    "errors"
    "fmt"
)

var KEY = []byte("095fc90fe8b18e8f243e4b07a9c0d170")
var IV = []byte("8bef55a546d27958ead1fdddba4d36ea")

func encrypt(myInput string) []byte {
    plaintext := []byte(myInput)

    // 创建Rijndael密码器
    block, err := rijndael256.NewCipher(KEY)
    if err != nil {
        panic(err)
    }

    plaintext, _ = pkcs7Pad(plaintext, block.BlockSize())
    // IV需要是唯一的,但不需要是安全的。因此,通常将其包含在密文的开头。
    ciphertext := make([]byte, rijndael256.BlockSize+len(plaintext))
    bm := cipher.NewCBCEncrypter(block, IV)
    bm.CryptBlocks(ciphertext[rijndael256.BlockSize:], plaintext)

    return ciphertext[rijndael256.BlockSize:]
}

// pkcs7Pad使用1到n个字节对给定的字节片进行右填充,其中n是块大小。结果的大小是x倍的n,其中x至少为1。
func pkcs7Pad(b []byte, blocksize int) ([]byte, error) {
    if blocksize <= 0 {
        return nil, errors.New("无效的块大小")
    }
    if b == nil || len(b) == 0 {
        return nil, errors.New("无效的PKCS7数据(空或未填充)")
    }
    n := blocksize - (len(b) % blocksize)
    pb := make([]byte, len(b)+n)
    copy(pb, b)
    copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
    return pb, nil
}

func main() {
    plainText := "{\"randJsonList\":[\"abc\"], \"name\":\"baron\", \"support\":\"king\"}"
    x := encrypt(plainText)

    outputString := base64.StdEncoding.EncodeToString(x)
    fmt.Println(outputString)
}

输出结果:

DdSUyoYRYW/zDNSVaA1JZ39WqJt06qp0FiJUlCW5BbZWEt41GzsmtgVnGZuHigZNs7qKhI+kHAKMXL8EPnK1vg==

与C#代码的输出结果相同。

英文:

The C# code uses Rijndael with a block size of 256 bits (see comments), a static IV (see comments) and returns only the ciphertext (i.e. without prepended IV).<br>
The Go code applies AES with by definition a block size of 128 bits, a randomly generated IV (the static IV in the code is ignored) and returns the concatenation of IV and ciphertext.

AES is a subset of Rijndael. Rijndael defines different block sizes and key sizes between 128 and 256 bits in 32 bit steps, see here. For AES only the block size 128 bits is defined and the key sizes 128, 192 and 256 bits. Note that the standard is AES and not Rijndael, so AES should be preferred over Rijndael (many libraries do not even implement Rijndael, but AES instead).

A static IV is insecure. Key/IV pairs must not repeat for security reasons. Therefore, in general, with a fixed key, a random IV is generated for each encryption. The IV is not secret and is passed along with the ciphertext to the decryption side, typically concatenated in the order IV|ciphertext.

Therefore, the current Go code is a secure implementation (even more secure is authenticated encryption e.g. via GCM), while the C# code is not. So it would make more sense to modify the C# code to be functionally equivalent to the Go code.<br>
However, since the C# code seems to be the reference, the following changes are needed in the Go code to make it functionally identical to the C# code:

  • Instead of AES, Rijndael must be applied. In the following example, pkg.go.dev/github.com/azihsoyn/rijndael256 is used. To do this, import &quot;github.com/azihsoyn/rijndael256&quot; and formally replace aes with rijndael256. You can of course apply another implementation.
  • The static IV is to be applied: bm := cipher.NewCBCEncrypter(block, IV).
    iv and its filling is to be removed together with associated imports.
  • Only the ciphertext is returned in the enecrypt()-method: return ciphertext[rijndael256.BlockSize:].

The following Go code gives the result of the C# code:

package main

import (
    &quot;bytes&quot;
    &quot;github.com/azihsoyn/rijndael256&quot;
    &quot;crypto/cipher&quot;
    &quot;encoding/base64&quot;
    &quot;errors&quot;
    &quot;fmt&quot;
)

var KEY = []byte(&quot;095fc90fe8b18e8f243e4b07a9c0d170&quot;)
var IV = []byte(&quot;8bef55a546d27958ead1fdddba4d36ea&quot;)

func encrypt(myInput string) []byte {
    plaintext := []byte(myInput)

    // Create the AES cipher
    block, err := rijndael256.NewCipher(KEY)
    if err != nil {
        panic(err)
    }

    plaintext, _ = pkcs7Pad(plaintext, block.BlockSize())
    // The IV needs to be unique, but not secure. Therefore it&#39;s common to
    // include it at the beginning of the ciphertext.
    ciphertext := make([]byte, rijndael256.BlockSize+len(plaintext))
    bm := cipher.NewCBCEncrypter(block, IV)
    bm.CryptBlocks(ciphertext[rijndael256.BlockSize:], plaintext)

    return ciphertext[rijndael256.BlockSize:]
}

// pkcs7Pad right-pads the given byte slice with 1 to n bytes, where
// n is the block size. The size of the result is x times n, where x
// is at least 1.
func pkcs7Pad(b []byte, blocksize int) ([]byte, error) {
    if blocksize &lt;= 0 {
        return nil, errors.New(&quot;invalid blocksize&quot;)
    }
    if b == nil || len(b) == 0 {
        return nil, errors.New(&quot;invalid PKCS7 data (empty or not padded)&quot;)
    }
    n := blocksize - (len(b) % blocksize)
    pb := make([]byte, len(b)+n)
    copy(pb, b)
    copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
    return pb, nil
}

func main() {
    plainText := &quot;{\&quot;randJsonList\&quot;:[ \&quot;abc\&quot; ], \&quot;name\&quot;:\&quot;baron\&quot;, \&quot;support\&quot;:\&quot;king\&quot;}&quot;
    x := encrypt(plainText)

    outputString := base64.StdEncoding.EncodeToString(x)
    fmt.Println(outputString)
}

The output:

DdSUyoYRYW/zDNSVaA1JZ39WqJt06qp0FiJUlCW5BbZWEt41GzsmtgVnGZuHigZNs7qKhI+kHAKMXL8EPnK1vg==

is equal to that of the C# code.

huangapple
  • 本文由 发表于 2021年9月12日 04:46:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/69146596.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定