简体   繁体   English

使用golang解密使用php openssl_encrypt加密的文件

[英]Using golang to decrypt file encrypted with php openssl_encrypt

First of all. 首先。 I'm on thin ice here! 我在这里冰薄上!

I have a encrypted file that I get from php. 我有一个从php获取的加密文件。 I'm trying to decrypt this with golang. 我正在尝试用golang解密。

The php application uses a public RSA key to encrypt the key used to encrypt with aes-256-cbc. php应用程序使用公共RSA密钥来加密用于使用aes-256-cbc加密的密钥。

I've created some proof of concept code, but I can't get it right. 我已经创建了一些概念证明代码,但是我做对了。 Even though key and iv look correct on both sides there is something that is not. 即使key和iv看起来都正确,但有些东西却不正确。 The result is just garbage. 结果只是垃圾。 I'm suspecting either some encoding mismatch (expecting base64, getting string bytes...something) or that I've misunderstood some concept. 我怀疑是某些编码不匹配(期望base64,正在获取字符串字节...某物),或者是我误解了一些概念。

Encrypting: 加密:

<?php

$cipher = "AES-256-CBC";
$ivLength = openssl_cipher_iv_length($cipher="AES-256-CBC");
echo "iv len: " . $ivLength . "\n";
$iv = openssl_random_pseudo_bytes($ivLength);
$key = "1234567890abcdef";

$ciphertext = openssl_encrypt("hello world", $cipher, $key, 0, $iv);

$publicKey = openssl_pkey_get_public(file_get_contents("some-public-key.pub"));
if (!$publicKey) {
   die("OpenSSL: Unable to get public key for encryption. Is the location correct? Does this key require a password?");
}

$ok = openssl_public_encrypt($key, $encryptedKey, $publicKey);
if (!$ok) {
    die("Encryption failed. Ensure you are using a PUBLIC key.");
}

echo "key unencrypted: " . $key . "\n";
echo "iv: " . base64_encode($iv) . "\n";
echo "ciphertext: " . $ciphertext . "\n";
echo "ciphertext binary: " . (base64_decode($ciphertext)) . "\n";
echo "combined: " . ($iv . $ciphertext) . "\n";

file_put_contents("key.enc", $encryptedKey);
file_put_contents("content.enc", $iv . $ciphertext);
file_put_contents("content.dec", openssl_decrypt($ciphertext, $cipher, $key, 0, $iv));

openssl_free_key($publicKey);
?>

Decrypting: 解密:

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/base64"
    "encoding/hex"
    "encoding/pem"
    "fmt"
    "io"
    "io/ioutil"
    "log"
)

func main() {

    // Read the input file
    in, err := ioutil.ReadFile("key.enc")
    if err != nil {
        log.Fatalf("input file: %s", err)
    }

    // Read the private key
    pemData, err := ioutil.ReadFile("some-private-key")
    if err != nil {
        log.Fatalf("read key file: %s", err)
    }

    // Extract the PEM-encoded data block
    block, _ := pem.Decode(pemData)
    if block == nil {
        log.Fatalf("bad key data: %s", "not PEM-encoded")
    }
    if got, want := block.Type, "RSA PRIVATE KEY"; got != want {
        log.Fatalf("unknown key type %q, want %q", got, want)
    }

    // Decode the RSA private key
    priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err != nil {
        log.Fatalf("bad private key: %s", err)
    }

    // Decrypt the data
    cipherKey, err := rsa.DecryptPKCS1v15(rand.Reader, priv, in)
    if err != nil {
        log.Fatalf("decrypt: %s", err)
    }

    fmt.Println("Key decrypted:", string(cipherKey))

    // Read encrypted content file
    content, err := ioutil.ReadFile("content.enc")
    if err != nil {
        log.Fatalf("input file: %s", err)
    }

    fmt.Println("Cipherkey: ", string(cipherKey))
    cipherText := content

    cipherBlock, err := aes.NewCipher(cipherKey)
    if err != nil {
        panic(err)
    }

    iv := cipherText[:aes.BlockSize]
    fmt.Println("iv:", base64.StdEncoding.EncodeToString(iv))
    fmt.Println("ciphertext:", string(cipherText[aes.BlockSize:]))
    cipherText, _ = base64.StdEncoding.DecodeString(string(cipherText[aes.BlockSize:]))
    fmt.Println("ciphertext binary: ", string(cipherText))

    // CBC mode always works in whole blocks.
    if len(cipherText)%aes.BlockSize != 0 {
        panic(fmt.Sprintf("ciphertext (len=%d) is not a multiple of the block size (%d)", len(cipherText), aes.BlockSize))
    }

    mode := cipher.NewCBCDecrypter(cipherBlock, iv)
    mode.CryptBlocks(cipherText, cipherText)

    fmt.Printf("The result: %s\n", cipherText)
}

Here's some example output from executing this (first php, then go): 这是执行此命令的一些示例输出(首先是php,然后是go):

iv len: 16
key unencrypted: 1234567890abcdef
iv: QffXbVRuwyopwwvQXQ8N6g==
ciphertext: Wk8Gv1xQWikp1YryQiywgQ==
ciphertext binary: ZO�\PZ))Պ�B,��
combined: A��mTn�*)�
�Wk8Gv1xQWikp1YryQiywgQ==
-----
Key decrypted: 1234567890abcdef
Cipherkey:  1234567890abcdef
iv: QffXbVRuwyopwwvQXQ8N6g==
ciphertext: Wk8Gv1xQWikp1YryQiywgQ==
ciphertext binary:  ZO�\PZ))Պ�B,��
The result: ��2��J���~A�D

Let's take a step back and simplify: 让我们退后一步并简化一下:

// encrypt.php
<?php

$iv = base64_decode("AJf3QItKM7+Lkh/BZT2xNg==");
$key = "1234567890abcdef";

echo openssl_encrypt("hello world", "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);


// decrypt.go
package main

import (
        "crypto/aes"
        "crypto/cipher"
        "encoding/base64"
        "fmt"
        "io/ioutil"
        "log"
        "os"
)

func main() {
        iv, _ := base64.StdEncoding.DecodeString("AJf3QItKM7+Lkh/BZT2xNg==")
        key := []byte("1234567890abcdef")

        text, _ := ioutil.ReadAll(os.Stdin)

        cipherBlock, err := aes.NewCipher(key)
        if err != nil {
            log.Fatal(err)
        }

        cipher.NewCBCDecrypter(cipherBlock, iv).CryptBlocks(text, text)
        fmt.Println(string(text))
}

If we run this, low and behold, we get garbage: 如果我们低调地运行此命令,则会得到垃圾:

$ php encrypt.php | go run decrypt.go 
7v>r

Note the distinct absence of the string 256 in the Go code. 请注意Go代码中字符串256的明显缺失。 Instead of requiring you to specify the key size it just, you know, looks what size the key is. 您知道,它并不仅仅是要求您指定密钥的大小,而是看起来密钥的大小。 In this case you defined a 16 byte/128 bit key. 在这种情况下,您定义了一个16字节/ 128位密钥。

If you specify AES-256 but then pass a 128 bit key to openssl, openssl pads the key with zeros until it is 256 bit long. 如果指定AES-256,然后将128位密钥传递给openssl,openssl将密钥填充为零,直到它的长度为256位。

Here are the possible fixes (in order of my personal preference): 以下是可能的修复(根据我的个人喜好):

Use a 256 bit key: 使用256位密钥:

--- encrypt.php.orig    2018-04-13 10:55:10.988913605 +0200
+++ encrypt.php.fix-key 2018-04-13 10:57:13.565673205 +0200
@@ -3,3 +3,3 @@
 $iv = base64_decode("AJf3QItKM7+Lkh/BZT2xNg==");
-$key = "1234567890abcdef";
+$key = "1234567890abcdef1234567890abcdef";

--- decrypt.go.orig     2018-04-13 10:55:17.083901651 +0200
+++ decrypt.go.fix-key  2018-04-13 10:55:49.467838139 +0200
@@ -14,3 +14,3 @@
        iv, _ := base64.StdEncoding.DecodeString("AJf3QItKM7+Lkh/BZT2xNg==")
-       key := []byte("1234567890abcdef")
+       key := []byte("1234567890abcdef1234567890abcdef")

In PHP, select a cipher method that matches the key: 在PHP中,选择与密钥匹配的密码方法:

--- encrypt.php.orig    2018-04-13 10:55:10.988913605 +0200
+++ encrypt.php.fix-method      2018-04-13 10:56:18.105781974 +0200
@@ -5,2 +5,2 @@

-echo openssl_encrypt("hello world", "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
+echo openssl_encrypt("hello world", "AES-128-CBC", $key, OPENSSL_RAW_DATA, $iv);

Do the zero padding in Go as well: 在Go中也执行零填充:

--- decrypt.go.orig     2018-04-13 10:55:17.083901651 +0200
+++ decrypt.go.pad-key  2018-04-13 10:56:39.601739816 +0200
@@ -14,3 +14,4 @@
        iv, _ := base64.StdEncoding.DecodeString("AJf3QItKM7+Lkh/BZT2xNg==")
-       key := []byte("1234567890abcdef")
+       key := make([]byte, 32)
+       copy(key, []byte("1234567890abcdef"))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM