如何复制 openssl_encrypt 函数?

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

how to duplicate openssl_encrypt?

问题

我希望有人已经在Golang中实现了这个功能,因为我对密码学一窍不通。然而,在将一个项目从PHP迁移到Golang时,我在迁移openssl_encrypt方法时遇到了问题,该方法可以在这里找到。我也研究了一下源代码,但没有找到解决办法。

这是我在Golang中实现的方法,它给我输出:

lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwCmLKkxoJN5Zf/ODOJ/RGq5

这是我在使用PHP时需要的输出:

lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwDV98XaJjvzEjBQp7jc+2DH

这是我在PHP中用于生成它的函数:

$data = "This is some text I want to encrypt";
$method = "aes-256-cbc";
$password = "This is a really long key and su";
$options = 0;
$iv = "MMMMMMMMMMMMMMMM";

echo openssl_encrypt($data, $method, $password, $options, $iv);

对我来说,看起来很接近,我一定是漏掉了什么明显的东西。

英文:

I was hoping someone had already implemented this in golang as I am far from even good at cryptography. However in porting a project from php to golang I have run into an issue with porting the openssl_encrypt method found here. I have also dug into the source code a little with no avail.

Here is the method I have implemented in golang. which gives me the output

lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwCmLKkxoJN5Zf/ODOJ/RGq5

Here is the output I need when using php.

lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwDV98XaJjvzEjBQp7jc+2DH

And here is the function I used to generate it with php.

$data = "This is some text I want to encrypt";
$method = "aes-256-cbc";
$password = "This is a really long key and su";
$options = 0;
$iv = "MMMMMMMMMMMMMMMM";

echo openssl_encrypt($data, $method, $password, $options, $iv);

To me it looks like it is very close and I must be missing something obvious.

答案1

得分: 4

你非常接近了,但是填充方式有误。根据这个答案(以及PHP文档),PHP使用默认的OpenSSL填充行为,即使用所需数量的填充字节作为填充字节值。

我所做的唯一更改是:

copy(plaintextblock[length:], bytes.Repeat([]byte{uint8(extendBlock)}, extendBlock))

你可以在这里看到完整的更新代码。

英文:

You were very close, but you had the padding wrong. According to this answer (and the PHP docs), PHP uses the default OpenSSL padding behavior, which is to use the required number of padding bytes as the padding byte value.

The only change I made was:

copy(plaintextblock[length:], bytes.Repeat([]byte{uint8(extendBlock)}, extendBlock))

You can see the full updated code here.

答案2

得分: 1

其他人在我玩耍的时候就已经给出了答案,但我有一个“更好”的修复版本的示例代码,它还考虑到了填充始终是必需的(至少要模拟php代码的行为)。

它还展示了你可以使用的openssl命令行,并且如果可用,会运行它(当然,playground不会运行)。

package main

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"fmt"
	"log"
	"os/exec"
	"strings"
)

func main() {
	const input = "This is some text I want to encrypt"
	fmt.Println(opensslCommand(input))
	fmt.Println(aesCBCenctypt(input))
}

func aesCBCenctypt(input string) string {
	// 当然,真正的IV应该来自crypto/rand
	iv := []byte("MMMMMMMMMMMMMMMM")
	// 真正的密钥应该来自类似PBKDF2、RFC 2898的东西。
	// 例如,使用golang.org/x/crypto/pbkdf2将
	// “口令”转换为密钥。
	key := []byte("This is a really long key and su")

	// 确保块大小是aes.BlockSize的倍数
	// 使用填充长度作为填充字节进行填充
	// 如果我们不需要填充,我们将填充一个完整的额外块。
	pad := (aes.BlockSize - len(input)%aes.BlockSize)
	if pad == 0 {
		pad = aes.BlockSize
	}
	data := make([]byte, len(input)+pad)
	copy(data, input)
	for i := len(input); i < len(input)+pad; i++ {
		data[i] = byte(pad)
	}

	cb, err := aes.NewCipher(key)
	if err != nil {
		log.Fatalln("error NewCipher():", err)
	}

	mode := cipher.NewCBCEncrypter(cb, iv)
	mode.CryptBlocks(data, data)
	return base64.StdEncoding.EncodeToString(data)
}

// 仅供比较,不要在真实环境中使用!
func opensslCommand(input string) string {
	iv := []byte("MMMMMMMMMMMMMMMM")
	key := []byte("This is a really long key and su")

	args := []string{"enc", "-aes-256-cbc", "-base64"}
	// "-nosalt", "-nopad"
	args = append(args, "-iv", fmt.Sprintf("%X", iv))
	args = append(args, "-K", fmt.Sprintf("%X", key))

	cmd := exec.Command("openssl", args...)
	// 显示如何通过命令行执行此操作:
	fmt.Println("Command:", strings.Join(cmd.Args, " "))

	cmd.Stdin = strings.NewReader(input)
	result, err := cmd.CombinedOutput()
	if err != nil {
		if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
			// openssl不可用
			return err.Error() // XXX
		}
		// 其他错误,显示它和(错误?)输出并退出
		fmt.Println("cmd error:", err)
		log.Fatalf("result %q", result)
	}
	// 去掉尾部的'\n'并返回
	if n := len(result) - 1; result[n] == '\n' {
		result = result[:n]
	}
	return string(result)
}

Playground

英文:

Others beat me to the answer while I was playing with it, but I have a "better" fixed version of your example code that also takes into account that padding is always required (at least to emulate what the php code does).

It also shows the openssl command line that you'd use to do the same thing, and if available runs it (of course the playground won't).

package main
import (
&quot;crypto/aes&quot;
&quot;crypto/cipher&quot;
&quot;encoding/base64&quot;
&quot;fmt&quot;
&quot;log&quot;
&quot;os/exec&quot;
&quot;strings&quot;
)
func main() {
const input = &quot;This is some text I want to encrypt&quot;
fmt.Println(opensslCommand(input))
fmt.Println(aesCBCenctypt(input))
}
func aesCBCenctypt(input string) string {
// Of course real IVs should be from crypto/rand
iv := []byte(&quot;MMMMMMMMMMMMMMMM&quot;)
// And real keys should be from something like PBKDF2, RFC 2898.
// E.g. use golang.org/x/crypto/pbkdf2 to turn a
// &quot;passphrase&quot; into a key.
key := []byte(&quot;This is a really long key and su&quot;)
// Make sure the block size is a multiple of aes.BlockSize
// Pad to aes.BlockSize using the pad length as the padding
// byte. If we would otherwise need no padding we instead
// pad an entire extra block.
pad := (aes.BlockSize - len(input)%aes.BlockSize)
if pad == 0 {
pad = aes.BlockSize
}
data := make([]byte, len(input)+pad)
copy(data, input)
for i := len(input); i &lt; len(input)+pad; i++ {
data[i] = byte(pad)
}
cb, err := aes.NewCipher(key)
if err != nil {
log.Fatalln(&quot;error NewCipher():&quot;, err)
}
mode := cipher.NewCBCEncrypter(cb, iv)
mode.CryptBlocks(data, data)
return base64.StdEncoding.EncodeToString(data)
}
// Just for comparison, don&#39;t do this for real!
func opensslCommand(input string) string {
iv := []byte(&quot;MMMMMMMMMMMMMMMM&quot;)
key := []byte(&quot;This is a really long key and su&quot;)
args := []string{&quot;enc&quot;, &quot;-aes-256-cbc&quot;, &quot;-base64&quot;}
// &quot;-nosalt&quot;, &quot;-nopad&quot;
args = append(args, &quot;-iv&quot;, fmt.Sprintf(&quot;%X&quot;, iv))
args = append(args, &quot;-K&quot;, fmt.Sprintf(&quot;%X&quot;, key))
cmd := exec.Command(&quot;openssl&quot;, args...)
// Show how you could do this via the command line:
fmt.Println(&quot;Command:&quot;, strings.Join(cmd.Args, &quot; &quot;))
cmd.Stdin = strings.NewReader(input)
result, err := cmd.CombinedOutput()
if err != nil {
if e, ok := err.(*exec.Error); ok &amp;&amp; e.Err == exec.ErrNotFound {
// openssl not available
return err.Error() // XXX
}
// some other error, show it and the (error?) output and die
fmt.Println(&quot;cmd error:&quot;, err)
log.Fatalf(&quot;result %q&quot;, result)
}
// Strip trailing &#39;\n&#39; and return it.
if n := len(result) - 1; result[n] == &#39;\n&#39; {
result = result[:n]
}
return string(result)
}

<kbd>Playground</kbd>

huangapple
  • 本文由 发表于 2015年3月21日 10:59:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/29178760.html
匿名

发表评论

匿名网友

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

确定