Skip to content
Advertisement

Matching blowfish encryption in php openssl_encrypt and golang blowfish

PHP:

$key = "testtesttest";
$text = "bublijuja";
$iv = openssl_random_pseudo_bytes( openssl_cipher_iv_length("blowfish"));
for($i = 0; $i < strlen($iv); $i++)
{
    echo ord($iv[$i])." ";
}
echo "n";
$et = openssl_encrypt( $text, "blowfish", $key, OPENSSL_RAW_DATA, $iv );
for($i = 0; $i < strlen($et); $i++)
{
    echo ord($et[$i])." ";
}
echo "n";

This prints(first line is IV, second one is encrypted text:

253 145 220 198 224 78 40 124 
208 51 12 30 46 92 13 181 19 210 50 57 174 207 93 130 

Copying that IV to golang:

package main

import (
    "golang.org/x/crypto/blowfish"
    "crypto/cipher"
    "fmt"
)



func main() {
    key  := "testtesttest"
    plaintext := "bublijuja"
    byteSlice := []int{253, 145, 220, 198, 224, 78, 40, 124}
    iv := make([]byte, len(byteSlice))
    for i, b := range byteSlice {
        iv[i] = byte(b)
    }
    fmt.Println(iv)
    ciphertext := make([]byte, blowfish.BlockSize+len(plaintext))
    block, err := blowfish.NewCipher([]byte(key))
    if err != nil {
    fmt.Println(err.Error())
    return
    }
    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext, pad([]byte(plaintext)))

    fmt.Println(ciphertext)
}


func pad(pt []byte) []byte {
    // calculate modulus of plaintext to blowfish's cipher block size
    // if result is not 0, then we need to pad
    modulus := len(pt) % blowfish.BlockSize
    if modulus != 0 {
        // how many bytes do we need to pad to make pt to be a multiple of
        //blowfish's block size?
        padlen := blowfish.BlockSize - modulus
        // let's add the required padding
        for i := 0; i < padlen; i++ {
            // add the pad, one at a time
            pt = append(pt, 0)
        }
    }
    // return the whole-multiple-of-blowfish.BlockSize-sized plaintext
    // to the calling function
    return pt
}

I get:

[253 145 220 198 224 78 40 124]
[61 82 97 183 42 220 119 173 114 107 250 139 174 236 113 91 0]

I Tried with ECB mode as well. I was able to match first 8 bytes once but I messed something up. I’m trying to figure out how the php version handles it so that I can match go implementation, but I’m failing so far.

Advertisement

Answer

The following issues cause the different results:

  • Blowfish has an 8 bytes block size and a variable key size between 4 and 56 bytes. In PHP there is a bug for Blowfish that pads shorter keys to 16 bytes with 0 values. Since version 7.1.8 there is a flag that prevents this: OPENSSL_DONT_ZERO_PAD_KEY. If this flag is additionally set (OPENSSL_RAW_DATA | OPENSSL_DONT_ZERO_PAD_KEY), the following output results (in the required environment):

    253 145 220 198 224 78 40 124 
    61 82 97 183 42 220 119 173 26 156 153 20 152 139 105 237 
    

    Here there is an online PHP environment where the flag can be set.

  • The padding defined in Go is Zero padding while in the PHP code PKCS7 padding is used by openssl (as default). For PKCS7 the following changes are necessary (without comments, using the same names):

    func pad(pt []byte) []byte {
        modulus := len(pt) % blowfish.BlockSize
        padlen := blowfish.BlockSize - modulus
        for i := 0; i < padlen; i++ {
            pt = append(pt, byte(padlen))
        }
        return pt
    }
    

    With this change the Go – Code gives the same result:

    [253 145 220 198 224 78 40 124]
    [61 82 97 183 42 220 119 173 26 156 153 20 152 139 105 237 0]
    

    The 0 at the end is caused by a too large buffer for the ciphertext. In the Go code the length of the output buffer is calculated with plaintext length plus block size (8 bytes for Blowfish), which ensures that there is enough space for padding, since the maximum padding is one block. With shorter padding the buffer is too large, e.g. in the current case the plaintext has a length of 9 bytes, which results in a buffer of 17 bytes. The ciphertext has a length of 16 bytes, which leads to the 0 at the end. If desired, the exactly required buffer size can be determined as plaintext length plus padding length (the latter being determined analogous to padlen in the pad function).

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement