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

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

报告相同问题?