drryyiuib43562604 2018-04-13 06:29
浏览 846
已采纳

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

First of all. I'm on thin ice here!

I have a encrypted file that I get from php. I'm trying to decrypt this with golang.

The php application uses a public RSA key to encrypt the key used to encrypt with 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. 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.

Encrypting:

<?php

$cipher = "AES-256-CBC";
$ivLength = openssl_cipher_iv_length($cipher="AES-256-CBC");
echo "iv len: " . $ivLength . "
";
$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 . "
";
echo "iv: " . base64_encode($iv) . "
";
echo "ciphertext: " . $ciphertext . "
";
echo "ciphertext binary: " . (base64_decode($ciphertext)) . "
";
echo "combined: " . ($iv . $ciphertext) . "
";

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
", cipherText)
}

Here's some example output from executing this (first php, then 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
  • 写回答

1条回答 默认 最新

  • douqiaolong0528 2018-04-13 08:28
    关注

    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. 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.

    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.

    Here are the possible fixes (in order of my personal preference):

    Use a 256 bit key:

    --- 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:

    --- 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:

    --- 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"))
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥30 Matlab打开默认名称带有/的光谱数据
  • ¥50 easyExcel模板 动态单元格合并列
  • ¥15 res.rows如何取值使用
  • ¥15 在odoo17开发环境中,怎么实现库存管理系统,或独立模块设计与AGV小车对接?开发方面应如何设计和开发?请详细解释MES或WMS在与AGV小车对接时需完成的设计和开发
  • ¥15 CSP算法实现EEG特征提取,哪一步错了?
  • ¥15 游戏盾如何溯源服务器真实ip?需要30个字。后面的字是凑数的
  • ¥15 vue3前端取消收藏的不会引用collectId
  • ¥15 delphi7 HMAC_SHA256方式加密
  • ¥15 关于#qt#的问题:我想实现qcustomplot完成坐标轴
  • ¥15 下列c语言代码为何输出了多余的空格