Расшифровка aes-256-gcm из PHP на Голанг - PullRequest
0 голосов
/ 02 октября 2018

У меня есть функция шифрования, которую я использую в PHP

function Encrypt(?string $Content, string $Key): string {
    return openssl_encrypt($Content, 'aes-256-gcm', $Key, OPENSSL_RAW_DATA, $IV = random_bytes(16), $Tag, '', 16) . $IV . $Tag;
}

В сочетании с функцией дешифрования

function Decrypt(?string $Ciphertext, string $Key): ?string {
    if (strlen($Ciphertext) < 32)
        return null;

    $Content = substr($Ciphertext, 0, -32);
    $IV = substr($Ciphertext, -32, -16);
    $Tag = substr($Ciphertext, -16);

    try {
        return openssl_decrypt($Content, 'aes-256-gcm', $Key, OPENSSL_RAW_DATA, $IV, $Tag);
    } catch (Exception $e) {
        return null;
    }
}

Я храню данные, зашифрованные с помощью функции шифрования, в мою базу данных, и теперьЯ пытаюсь расшифровать те же значения в Go, но получаю cipher: message authentication failed и не могу понять, что мне не хватает.

c := []byte(`encrypted bytes of sorts`) // the bytes from the db

content := c[:len(c)-32]
iv := c[len(c)-32 : len(c)-16]
tag := c[len(c)-16:]

block, err := aes.NewCipher(key[:32])
if err != nil {
    panic(err.Error())
}

aesgcm, err := cipher.NewGCMWithNonceSize(block, 16)
if err != nil {
    panic(err.Error())
}

fmt.Println(aesgcm.NonceSize(), aesgcm.Overhead()) // making sure iv and tag are both 16 bytes

plaintext, err := aesgcm.Open(nil, iv, append(content, tag...), nil)
if err != nil {
    panic(err.Error())
}

Стоит отметить, что ключ I 'm использует не 32 байта (это намного больше), так как я не знал, что необходимый ключ / должен быть 32 байта, поэтому я не совсем уверен, что делает с ним PHP (как при усечении его до 32 против хешированияэто что-то, имеющее 32 байта выходных данных по сравнению с чем-то другим).

Глядя на функцию Open из источника Go, похоже, что тег должен быть последним байтом «размера тега» текста,вот почему я добавляю тег к зашифрованному тексту после разбора частей.

// copied from C:\Go\src\crypto\cipher\gcm.go, Go version 1.11
func (g *gcm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
    if len(nonce) != g.nonceSize {
        panic("cipher: incorrect nonce length given to GCM")
    }

    if len(ciphertext) < gcmTagSize {
        return nil, errOpen
    }
    if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(g.cipher.BlockSize())+gcmTagSize {
        return nil, errOpen
    }

    tag := ciphertext[len(ciphertext)-gcmTagSize:]
    ciphertext = ciphertext[:len(ciphertext)-gcmTagSize]

    var counter, tagMask [gcmBlockSize]byte
    g.deriveCounter(&counter, nonce)

    g.cipher.Encrypt(tagMask[:], counter[:])
    gcmInc32(&counter)

    var expectedTag [gcmTagSize]byte
    g.auth(expectedTag[:], ciphertext, data, &tagMask)

    ret, out := sliceForAppend(dst, len(ciphertext))

    if subtle.ConstantTimeCompare(expectedTag[:], tag) != 1 {
        // The AESNI code decrypts and authenticates concurrently, and
        // so overwrites dst in the event of a tag mismatch. That
        // behavior is mimicked here in order to be consistent across
        // platforms.
        for i := range out {
            out[i] = 0
        }
        return nil, errOpen
    }

    g.counterCrypt(out, ciphertext, &counter)

    return ret, nil
}

Пример PHP с использованием функций выше

$Key = 'outspoken outburst treading cramp cringing';

echo bin2hex($Enc = Encrypt('yeet', $Key)), '<br>'; // 924b3ba418f49edc1757f3fe88adcaa7ec4c1e7d15811fd0b712b0b091433073f6a38d7b
var_export(Decrypt($Enc, $Key)); // 'yeet'

Go

c, err := hex.DecodeString(`924b3ba418f49edc1757f3fe88adcaa7ec4c1e7d15811fd0b712b0b091433073f6a38d7b`)
if err != nil {
    panic(err.Error())
}
key := []byte(`outspoken outburst treading cramp cringing`)

content := c[:len(c)-32]
iv := c[len(c)-32 : len(c)-16]
tag := c[len(c)-16:]

block, err := aes.NewCipher(key[:32])
if err != nil {
    panic(err.Error())
}

aesgcm, err := cipher.NewGCMWithNonceSize(block, 16)
if err != nil {
    panic(err.Error())
}

ciphertext := append(content, tag...) // or `ciphertext := content`, same error

plaintext, err := aesgcm.Open(nil, iv, ciphertext, nil)
if err != nil {
    panic(err.Error()) // panic: cipher: message authentication failed
}

1 Ответ

0 голосов
/ 02 октября 2018

Обычно зашифрованное сообщение выглядит как IV+ciphertext+tag, а не ciphertext+IV+tag.Когда кто-то отклоняется от соглашения, он сталкивается со всеми видами проблем: -)

Вы видели, что происходит с iv срезом после вызова append(ciphertext, tag...)?Вы по существу перезаписываете iv с помощью tag:

Before:
924b3ba4 18f49edc1757f3fe88adcaa7ec4c1e7d 15811fd0b712b0b091433073f6a38d7b
After:
924b3ba4 15811fd0b712b0b091433073f6a38d7b 15811fd0b712b0b091433073f6a38d7b

. В качестве быстрого решения сделайте копию iv перед вызовом append():

iv := make([]byte, 16)
copy(iv, c[len(c)-32 : len(c)-16])

Более подробную информацию о ломтиках можно найти здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...