Search code examples
phpgoencryptionaescbc-mac

AES-256-CBC encryption Golang and PHP


I am trying to achieve AES-256-CBC encryption in golang. I have a working PHP code that has been used for years. I am getting encrypted values in Golang, but the values don't match the output from PHP one, for the same payload/key/iv combination.

For simplification I am hardcoding the payload/key/iv in below codes. I have also removed verbose error messaging from my go code.

This is my GO code

func encryption() {
    plaintext := []byte("12345678912345678912345678900000")
    key, _ := base64.StdEncoding.DecodeString("cidRgzwfcgztwae/mccalIeedOAmA/CbU3HEqWz1Ejk=")
    iv, _ := hex.DecodeString("162578ddce177a4a7cb2f7c738fa052d")

    /*php seem to use PKCS7 padding to make the source string match the blocksize 
     requirement for AES-256-CBC.
     From what I understand, I need to do padding manually in Golang. 
     Correct me if wrong */
    plaintext, _ = Pkcs7Pad(plaintext, aes.BlockSize)

    block, _ := aes.NewCipher(key)

    ciphertext := make([]byte, aes.BlockSize+len(plaintext))

    mode := cipher.NewCBCEncrypter(block, iv)

    mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)

    fmt.Printf("EncryptedText %v\n", string(ciphertext))
    fmt.Printf("EncryptedText as hex %v\n", hex.EncodeToString(ciphertext))
}

func Pkcs7Pad(b []byte, blocksize int) ([]byte, error) {
    if blocksize <= 0 {
        return nil, errors.New("Invalid block size")
    }
    if b == nil || len(b) == 0 {
        return nil, errors.New("Invalid block size")
    }
    n := blocksize - (len(b) % blocksize)
    pb := make([]byte, len(b)+n)
    copy(pb, b)
    copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
    return pb, nil
}

And my Go output is

EncryptedText |8??X?z??F ?0ĺe?,??G?V?
Gce??dM????z?,*ȁҼ
EncryptedText as hex 7c38bad658907a81d14620c930c4ba658c1f022cdb1392479856cc0a471d6365dfc5644db6b28cef7ac02c2ac881d2bc

I have a PHP code to do the same task, which gives me a different output.

function encryption() {
    $plaintext = "12345678912345678912345678900000";
    $key = base64_decode("cidRgzwfcgztwae/mccalIeedOAmA/CbU3HEqWz1Ejk=");
    $iv = hex2bin("162578ddce177a4a7cb2f7c738fa052d");

    //php openssl_encrypt function seem to use pkcs7pad to make the source string match the blocksize requirement for AES-256-CBC

    $ciphertext = openssl_encrypt($plaintext, 'AES-256-CBC', $key, 0, $iv );
    print $ciphertext;
}

and the PHP output is

fDi61liQeoHRRiDJMMS6ZYwfAizbE5JHmFbMCkcdY2XfxWRNtrKM73rALCrIgdK8

Obviously, I would like to get my Golang implementation to get the same output as PHP. As I require communication with my Golang code and existing PHP code, I would like to get encryption and decryption work same in both PHP and Golang.

Any ideas?


Solution

  • There are a few issues with your approach:

    • openssl_encrypt, by default, outputs a base 64 encoded string (not hex)
    • I suspect your pkcs7Pad (which you did not include) is doing something unexpected (due to the fact that you are skipping the start of ciphertext in mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)

    Note: I cannot replicate your result because you did not include your pkcs7Pad (a link to the playground is a good idea because it ensures others will be able to replicate your issue).

    I believe the code below will give what you are looking for (the result matches the php - I have not done any further testing):

    
    func main() {
        plaintext := []byte("12345678912345678912345678900000")
        key, err := base64.StdEncoding.DecodeString("cidRgzwfcgztwae/mccalIeedOAmA/CbU3HEqWz1Ejk=")
        if err != nil {
            panic(err)
        }
        iv, err := hex.DecodeString("162578ddce177a4a7cb2f7c738fa052d")
        if err != nil {
            panic(err)
        }
    
        plaintext = pkcs7Pad(plaintext, aes.BlockSize)
    
        block, err := aes.NewCipher(key)
        if err != nil {
            panic(err)
        }
    
        ciphertext := make([]byte, len(plaintext))
    
        mode := cipher.NewCBCEncrypter(block, iv)
    
        mode.CryptBlocks(ciphertext, plaintext)
    
        fmt.Printf("EncryptedText %v\n", string(ciphertext))
        fmt.Printf("EncryptedText as hex %v\n", hex.EncodeToString(ciphertext))
        fmt.Printf("EncryptedText as base 64 %v\n", base64.StdEncoding.EncodeToString(ciphertext))
    }
    
    func pkcs7Pad(ciphertext []byte, blockSize int) []byte {
        padding := blockSize - len(ciphertext)%blockSize
        padtext := bytes.Repeat([]byte{byte(padding)}, padding)
        return append(ciphertext, padtext...)
    }
    

    Try it in the playground