1

I was hoping someone had already implemented this in golang as I am far from even good at cryptography. However in porting a project from php to golang I have run into an issue with porting the openssl_encrypt method found here. I have also dug into the source code a little with no avail.

Here is the method I have implemented in golang. which gives me the output

lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwCmLKkxoJN5Zf/ODOJ/RGq5

Here is the output I need when using php.

lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwDV98XaJjvzEjBQp7jc+2DH

And here is the function I used to generate it with php.

$data = "This is some text I want to encrypt";
$method = "aes-256-cbc";
$password = "This is a really long key and su";
$options = 0;
$iv = "MMMMMMMMMMMMMMMM";

echo openssl_encrypt($data, $method, $password, $options, $iv);

To me it looks like it is very close and I must be missing something obvious.

michael.schuett
  • 4,248
  • 4
  • 28
  • 39
  • 1
    The PHP output is actually `lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwDV98XaJjvzEjBQp7jc+2DH` (Note the last symbol is missing). – lsowen Mar 21 '15 at 04:27
  • @JasonCoco would you care to elaborate? Like i said above I don't know a lot about crypto. But that is besides the point this is the encryption that a third party uses. However if you link me to some articles on why using CBC is bad ill look into it and try and change some peoples minds. – michael.schuett Mar 21 '15 at 23:19
  • 1
    Sorry, it's my bad, use cbc, don't use ecb, which you weren't using at all :) Apologizes for the misleading comment and I promise to sleep more before commenting in the future! – Jason Coco Mar 22 '15 at 20:41
  • @JasonCoco no problem at all just wanted to make sure I wasn't doing something stupid. – michael.schuett Mar 22 '15 at 22:14

2 Answers2

4

You were very close, but you had the padding wrong. According to this answer (and the PHP docs), PHP uses the default OpenSSL padding behavior, which is to use the required number of padding bytes as the padding byte value.

The only change I made was:

copy(plaintextblock[length:], bytes.Repeat([]byte{uint8(extendBlock)}, extendBlock))

You can see the full updated code here.

Community
  • 1
  • 1
lsowen
  • 3,728
  • 1
  • 21
  • 23
  • Thank you so much, Been trying to figure this one out for a while. The answer you linked to helped me understand padding better. – michael.schuett Mar 21 '15 at 04:31
  • 1
    Glad I could help. Just FYI, looks like you *always* have to use padding, so you might have to change your code some to still use padding even when the input plaintext is exactly a multiple of the block size. – lsowen Mar 21 '15 at 04:33
  • Thank you very much! Saved me a lot of time – Anatoly Bubenkov Mar 03 '18 at 11:38
1

Others beat me to the answer while I was playing with it, but I have a "better" fixed version of your example code that also takes into account that padding is always required (at least to emulate what the php code does).

It also shows the openssl command line that you'd use to do the same thing, and if available runs it (of course the playground won't).

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "encoding/base64"
    "fmt"
    "log"
    "os/exec"
    "strings"
)

func main() {
    const input = "This is some text I want to encrypt"
    fmt.Println(opensslCommand(input))
    fmt.Println(aesCBCenctypt(input))
}

func aesCBCenctypt(input string) string {
    // Of course real IVs should be from crypto/rand
    iv := []byte("MMMMMMMMMMMMMMMM")
    // And real keys should be from something like PBKDF2, RFC 2898.
    // E.g. use golang.org/x/crypto/pbkdf2 to turn a
    // "passphrase" into a key.
    key := []byte("This is a really long key and su")

    // Make sure the block size is a multiple of aes.BlockSize
    // Pad to aes.BlockSize using the pad length as the padding
    // byte. If we would otherwise need no padding we instead
    // pad an entire extra block.
    pad := (aes.BlockSize - len(input)%aes.BlockSize)
    if pad == 0 {
        pad = aes.BlockSize
    }
    data := make([]byte, len(input)+pad)
    copy(data, input)
    for i := len(input); i < len(input)+pad; i++ {
        data[i] = byte(pad)
    }

    cb, err := aes.NewCipher(key)
    if err != nil {
        log.Fatalln("error NewCipher():", err)
    }

    mode := cipher.NewCBCEncrypter(cb, iv)
    mode.CryptBlocks(data, data)
    return base64.StdEncoding.EncodeToString(data)
}

// Just for comparison, don't do this for real!
func opensslCommand(input string) string {
    iv := []byte("MMMMMMMMMMMMMMMM")
    key := []byte("This is a really long key and su")

    args := []string{"enc", "-aes-256-cbc", "-base64"}
    // "-nosalt", "-nopad"
    args = append(args, "-iv", fmt.Sprintf("%X", iv))
    args = append(args, "-K", fmt.Sprintf("%X", key))

    cmd := exec.Command("openssl", args...)
    // Show how you could do this via the command line:
    fmt.Println("Command:", strings.Join(cmd.Args, " "))

    cmd.Stdin = strings.NewReader(input)
    result, err := cmd.CombinedOutput()
    if err != nil {
        if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
            // openssl not available
            return err.Error() // XXX
        }
        // some other error, show it and the (error?) output and die
        fmt.Println("cmd error:", err)
        log.Fatalf("result %q", result)
    }
    // Strip trailing '\n' and return it.
    if n := len(result) - 1; result[n] == '\n' {
        result = result[:n]
    }
    return string(result)
}

Playground

Dave C
  • 7,729
  • 4
  • 49
  • 65