1

学習目的で、単純な AES-256-GCM 暗号化と復号化を実装しました。文字列の長さを 6 の倍数で入力した場合にコードをテストすると、正しい出力が得られますが、それ以外の場合は、復号化されたデータにガベージ文字が追加されます。

Case1:
Enter string: abcdef
Enter key: sdasdasdsa
-^%�
abcdef
6

Case2:
Enter string: abcdefghi
Enter key: sadsadsad
\h�,�[�
abcdefghi�\�
-1

今、私はhttps://www.openssl.org/docs/crypto/EVP_EncryptFinal_ex.htmlを読みました

EVP_DecryptFinal() will return an error code if padding is enabled and the
final block is not correctly formatted.

しかし、この場合、パディングはデフォルトで有効になっているため、問題は最終ブロックの正しいフォーマットにあると推測しています。以下にコードを添付しました。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>

void handleErrors()
{
    printf("Some error occured\n");
}

int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *aad,
    int aad_len, unsigned char *key, unsigned char *iv,
    unsigned char *ciphertext, unsigned char *tag)
{
    EVP_CIPHER_CTX *ctx;

    int len, ciphertext_len=0;

    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new()))
        handleErrors();

    /* Initialise the encryption operation. */
    if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
        handleErrors();

    /* Set IV length if default 12 bytes (96 bits) is not appropriate */
    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL))
        handleErrors();

    /* Initialise key and IV */
    if(1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) handleErrors();

    /* Provide any AAD data. This can be called zero or more times as
     * required
     */
    if(1 != EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len))
        handleErrors();

    /* Provide the message to be encrypted, and obtain the encrypted output.
     * EVP_EncryptUpdate can be called multiple times if necessary
     */
    if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
        handleErrors();
    ciphertext_len+= len;

    /* Finalise the encryption. Normally ciphertext bytes may be written at
     * this stage, but this does not occur in GCM mode
     */
    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) handleErrors();
    ciphertext_len += len;

    /* Get the tag */
    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag))
        handleErrors();

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);

    return ciphertext_len;
}


int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *aad,
    int aad_len, unsigned char *tag, unsigned char *key, unsigned char *iv,
    unsigned char *plaintext)
{
    EVP_CIPHER_CTX *ctx;
    int len, plaintext_len=0, ret;

    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new())) 
        handleErrors();

    /* Initialise the decryption operation. */
    if(!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
        handleErrors();

    /* Set IV length. Not necessary if this is 12 bytes (96 bits) */
    if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL))
        handleErrors();

    /* Initialise key and IV */
    if(!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) handleErrors();

    /* Provide any AAD data. This can be called zero or more times as
     * required
     */
    if(!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len))
        handleErrors();

    /* Provide the message to be decrypted, and obtain the plaintext output.
     * EVP_DecryptUpdate can be called multiple times if necessary
     */
    if(!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
        handleErrors();
    plaintext_len+= len;

    /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
    if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag))
        handleErrors();

    /* Finalise the decryption. A positive return value indicates success,
     * anything else is a failure - the plaintext is not trustworthy.
     */
    ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);

    if(ret > 0)
    {
        /* Success */
        plaintext_len += len;
        return plaintext_len;
    }
    else
    {
        /* Verify failed */
        return -1;
    }
}


int main (void)
{
    unsigned char str[1024],key[10],ciphertext[1024+EVP_MAX_BLOCK_LENGTH],tag[100],pt[1024];
    unsigned char iv[]="1234567890abcdef";
    unsigned char aad[]="1234567890123456";
    int k;
    printf("Enter string: ");
    scanf("%s",str);
    printf("Enter key: ");
    scanf("%s",key);

    encrypt(str, strlen(str), aad, strlen(aad), key, iv, ciphertext, tag);
    printf("%s\n",ciphertext);
    k = decrypt(ciphertext, strlen(ciphertext), aad, strlen(aad), tag, key, iv, pt);
    printf("%s\n%d\n",pt,k);
}
4

2 に答える 2

3

decrypt(ciphertext, strlen(ciphertext), aad, strlen(aad), tag, key, iv, pt);

decrypt(ciphertext, strlen(ciphertext), ...間違っている。暗号文に NULL が埋め込まれている可能性があり、その場合は切り捨てられます。あなたの場合、追加の文字がdecrypt関数に供給されています。その数を言うのは難しい -strlenたまたまメモリ内で NULL に遭遇するまで。

encryptの戻り値をキャプチャし、decrpytさまざまな長さを適切に設定する必要があります。

int x;
...

x = encrypt(str, strlen(str), aad, strlen(aad), key, iv, ciphertext, tag);
...

x = decrypt(ciphertext, x, aad, strlen(aad), tag, key, iv, pt);
...

にも同じ問題があるかもしれませんが、aad, strlen(aad)まだ明らかになっていないと思います。


復号化のための EVP_DecryptFinal_ex の最終ブロックの正しい形式は何ですか?

タイトルの質問に戻ると、何もありません。あなたの問題は別の場所にあります。


unsigned char *plaintext関数で使用されるバッファにオーバーフローが発生している可能性がありdecryptます。あなたは長さを渡していないので、decryptその長さを超えて喜んで書いています...

于 2015-01-04T10:35:20.053 に答える
2

上記の例では、キー、IV データを文字列として定義しています。キーと IV は、可能なバイトを含まないため、文字列で構成されるべきではありません。これは、セキュリティの量を最大化するために必要です。現在、可能なキーの量を制限しています。キーは、出力がランダムと区別できない関数によって生成される必要があります。

パスワードからキーを作成するには、PBKDF2 などのパスワード キー導出関数 (PBKDF) を使用する必要があります。IV はランダムで、暗号文とともに送信する必要があります。IV とキーは静的なサイズにする必要があります。IV は 16 バイト、キーは 16、24、または 32 バイトです。

ただし、テストするには、単純な配列の初期化を使用できます (AES-256 を使用しているため、キーに 32 バイト):

unsigned char key[32] = { 0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,
                          0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,
                          0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,
                          0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00 };
unsigned char iv[16] = { 0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,
                         0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00 };

または、関数に正しいサイズを指定する限り、X バイトのメモリを割り当てて値を入力することもできます。

You need to replace all occurrences of strlen() with sizeof(), except for plaintext and ciphertext. In the former you are actually encrypting a string, so strlen makes sense. In the latter you need to use the result of the encrypt operation. The ciphertext is contained within a buffer of the length (correctly) returned by your encryption method.

In the end modern ciphers operate on bytes instead of characters, so you need to provide the size in bytes for all input. This kind of issue is present for any language that treats bytes and characters using the same primitive type (char for the C-language of course). It also tends to hide encoding/decoding issues (e.g. with regards to UTF-8).

If you want to handle unknown lengths then you have to call EVP_EncryptUpdate or EVP_DecryptUpdate multiple times, keeping score on how many bytes are returned (as you are doing now). Then at the end of the input you simply call the update method a final time and then call EVP_EncryptFinal_ex or EVP_DecryptFinal_ex. In that case you should of course refactor your method into an init/update/final part and use some buffer for the input and output.

于 2015-01-04T13:25:01.747 に答える