AES-шифрование в PHP - AES-шифрование в GO - PullRequest
0 голосов
/ 23 октября 2019

Я работаю над устаревшим кодом, написанным на PHP, который мне нужно перенести на GO. Мы обязаны хранить зашифрованные данные и расшифровывать по требованию.

Поскольку устаревший код все еще должен запускаться до тех пор, пока все не будет перенесено в GO, мне нужно использовать те же методы шифрования / дешифрования в GO.

Я боролся с этой проблемой дляпоследние 10 часов. Я перепробовал (я думаю) все предложения Stackoverflow + многие другие.

Серверы работают на OpenSSL 1.1.1a. Я нашел и протестировал что-то полезное, но безуспешно:

https://dequeue.blogspot.com/2014/11/decrypting-something-encrypted-with.html

Мне не нужно много знаний о шифровании в целом, но я могу понять его основы.

Итак, если кто-нибудь достаточно любезен, чтобы дать мне несколько советов ... пожалуйста.

Устаревший код PHP

    var $encryption_key = 'XXX';
    var $cipher = 'aes-256-cbc';

    function encrypt($text) {
        global $encryption_key, $cipher;

        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));
        $data = openssl_encrypt($text, $cipher, $encryption_key, OPENSSL_RAW_DATA, $iv);

        return base64_encode($iv . $data);
    }

    function decrypt($text) {
        global $encryption_key, $cipher;

        $data = base64_decode($text);
        $iv_len = openssl_cipher_iv_length($cipher);
        $iv = substr($data, 0, $iv_len);
        $data = substr($data, $iv_len);

        return openssl_decrypt($data, $cipher, $encryption_key, OPENSSL_RAW_DATA, $iv);
    }

Последний код, проверенный в GO для дешифрования данных (игнорируйте блок ниже и прокрутите обновленный код)


    package main

    import (
        "crypto/aes"
        "crypto/cipher"
        "crypto/md5"
        "encoding/base64"
        "fmt"
    )

    func main(){
        result, _ := DecryptString(`password`, `U2FsdGVkX1+ywYxveBnekSnx6ZP25nyPsWHS3oqcuTo=`)
        fmt.Printf("Decrypted string is: %s", result)
    }

    var openSSLSaltHeader string = "Salted_" // OpenSSL salt is always this string + 8 bytes of actual salt

    type OpenSSLCreds struct {
        key []byte
        iv  []byte
    }

    // Decrypt string that was encrypted using OpenSSL and AES-256-CBC
    func DecryptString(passphrase, encryptedBase64String string) ([]byte, error) {
        data, err := base64.StdEncoding.DecodeString(encryptedBase64String)
        if err != nil {
            return nil, err
        }
        saltHeader := data[:aes.BlockSize]
        if string(saltHeader[:7]) != openSSLSaltHeader {
            return nil, fmt.Errorf("Does not appear to have been encrypted with OpenSSL, salt header missing.")
        }
        salt := saltHeader[8:]
        creds, err := extractOpenSSLCreds([]byte(passphrase), salt)
        if err != nil {
            return nil, err
        }
        return decrypt(creds.key, creds.iv, data)
    }

    func decrypt(key, iv, data []byte) ([]byte, error) {
        if len(data) == 0 || len(data)%aes.BlockSize != 0 {
            return nil, fmt.Errorf("bad blocksize(%v), aes.BlockSize = %v\n", len(data), aes.BlockSize)
        }
        c, err := aes.NewCipher(key)
        if err != nil {
            return nil, err
        }
        cbc := cipher.NewCBCDecrypter(c, iv)
        cbc.CryptBlocks(data[aes.BlockSize:], data[aes.BlockSize:])
        out, err := pkcs7Unpad(data[aes.BlockSize:], aes.BlockSize)
        if out == nil {
            return nil, err
        }
        return out, nil
    }

    // openSSLEvpBytesToKey follows the OpenSSL (undocumented?) convention for extracting the key and IV from passphrase.
    // It uses the EVP_BytesToKey() method which is basically:
    // D_i = HASH^count(D_(i-1) || password || salt) where || denotes concatentaion, until there are sufficient bytes available
    // 48 bytes since we're expecting to handle AES-256, 32bytes for a key and 16bytes for the IV
    func extractOpenSSLCreds(password, salt []byte) (OpenSSLCreds, error) {
        m := make([]byte, 48)
        prev := []byte{}
        for i := 0; i < 3; i++ {
            prev = hash(prev, password, salt)
            copy(m[i*16:], prev)
        }
        return OpenSSLCreds{key: m[:32], iv: m[32:]}, nil
    }

    func hash(prev, password, salt []byte) []byte {
        a := make([]byte, len(prev)+len(password)+len(salt))
        copy(a, prev)
        copy(a[len(prev):], password)
        copy(a[len(prev)+len(password):], salt)
        return md5sum(a)
    }

    func md5sum(data []byte) []byte {
        h := md5.New()
        h.Write(data)
        return h.Sum(nil)
    }

    // pkcs7Unpad returns slice of the original data without padding.
    func pkcs7Unpad(data []byte, blocklen int) ([]byte, error) {
        if blocklen <= 0 {
            return nil, fmt.Errorf("invalid blocklen %d", blocklen)
        }
        if len(data)%blocklen != 0 || len(data) == 0 {
            return nil, fmt.Errorf("invalid data len %d", len(data))
        }
        padlen := int(data[len(data)-1])
        if padlen > blocklen || padlen == 0 {
            return nil, fmt.Errorf("invalid padding")
        }
        pad := data[len(data)-padlen:]
        for i := 0; i < padlen; i++ {
            if pad[i] != byte(padlen) {
                return nil, fmt.Errorf("invalid padding")
            }
        }
        return data[:len(data)-padlen], nil
    }

Код, написанный на GO, должен быть в состоянии расшифровать данные, зашифрованные в PHP. Единственный результат, который я получаю, - это ошибка: Ошибка: не похоже, что она была зашифрована с помощью OpenSSL, отсутствует солевой заголовок.

ОБНОВЛЕНИЕ: Код обновления обновлен

Код ниже почти работает. IV, ключ, шифрованный двоичный текст все те же, что и в PHP, но окончательный результат все еще зашифрован (или неправильно расшифрован)


    package main

    import (
        "crypto/aes"
        "crypto/cipher"
        "encoding/base64"
        "fmt"
    )

    func main() {

        var stringToDecode string = "base64_encode(SOME_PHP_ENCRYPTED_USING_THE_PHP_CODE_ABOVE)"
        var cipherKey = []byte("myprivatekey")

        content, err := base64.StdEncoding.DecodeString(stringToDecode)
        if err != nil {
            fmt.Printf("Error: %s", err)
        }

        fmt.Println("Cipher key: ", string(cipherKey))
        fmt.Println("Cipher key length: ", len(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("Cipher text:", string(cipherText[aes.BlockSize:]))

        cipherText = cipherText[aes.BlockSize:]

        fmt.Println("Cipher text binary: ", string(cipherText))

        if len(cipherText)%aes.BlockSize != 0 {
            panic(fmt.Sprintf("Cipher text (len=%d) is not a multiple of the block size (%d)", len(cipherText), aes.BlockSize))
        }

        mode := cipher.NewCBCDecrypter(cipherBlock, iv)
        mode.CryptBlocks(cipherText, cipherText)

        // The output is not decrypted as expected
        fmt.Printf("The result: %s\n", string(cipherText))
    }

...