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