首页 > 解决方案 > 串行版本和并行结果不显示相同的输出

问题描述

我实际上是 openmp 的新手,我有一个有效的 aes-128-cbc 玩具代码,可以将硬编码的密文解密为 12345,这本书是由社区用户推荐给我的,我也遇到了这个openmp 参考指南,最后我受到社区用户之一的大力指导。从那些书和指南中,我试图并行化下面的串行代码

串行工作代码:

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <string.h>
#include <stdio.h>
#include <time.h>


int success = 0;

void handleOpenSSLErrors(void)
{
    ERR_print_errors_fp(stderr);
    abort();
}

unsigned char* decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *iv ){

    EVP_CIPHER_CTX *ctx;
    unsigned char *plaintexts;
    int len;
    int plaintext_len;

    unsigned char* plaintext = malloc(ciphertext_len);
    bzero(plaintext,ciphertext_len);

    /* Create and initialise the context */

    if(!(ctx = EVP_CIPHER_CTX_new())) handleOpenSSLErrors();

    /* Initialise the decryption operation. IMPORTANT - ensure you use a key
    * and IV size appropriate for your cipher
    * IV size for *most* modes is the same as the block size. For AES this
    * is 128 bits */

    if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv))
        handleOpenSSLErrors();


    EVP_CIPHER_CTX_set_key_length(ctx, EVP_MAX_KEY_LENGTH);

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

    plaintext_len = len;

    /* Finalise the decryption. Further plaintext bytes may be written at
    * this stage.
    */

    // return 1 if decryption successful, otherwise 0
    if(1 == EVP_DecryptFinal_ex(ctx, plaintext + len, &len))
        success = 1;
    plaintext_len += len;


    /* Add the null terminator */
    plaintext[plaintext_len] = 0;

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);
    //delete [] plaintext;
    return plaintext;
}


size_t calcDecodeLength(char* b64input) {
    size_t len = strlen(b64input), padding = 0;

    if (b64input[len-1] == '=' && b64input[len-2] == '=') //last two chars are =
        padding = 2;
    else if (b64input[len-1] == '=') //last char is =
        padding = 1;
    return (len*3)/4 - padding;
}

void Base64Decode( char* b64message, unsigned char** buffer, size_t* length) {


    BIO *bio, *b64;  // A BIO is an I/O strean abstraction

    int decodeLen = calcDecodeLength(b64message);
    *buffer = (unsigned char*)malloc(decodeLen + 1);
    (*buffer)[decodeLen] = '\0';

    bio = BIO_new_mem_buf(b64message, -1);
    b64 = BIO_new(BIO_f_base64());
    bio = BIO_push(b64, bio);

    //BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); //Do not use newlines to flush buffer
    *length = BIO_read(bio, *buffer, strlen(b64message));
    BIO_free_all(bio);
}

void initAES(const unsigned char *pass, unsigned char* salt, unsigned char* key, unsigned char* iv )
{
    //initialisatio of key and iv with 0
    bzero(key,sizeof(key));
    bzero(iv,sizeof(iv));

    EVP_BytesToKey(EVP_aes_128_cbc(), EVP_sha1(), salt, pass, strlen(pass), 1, key, iv);
}


int checkPlaintext(char* plaintext, char* result){

    int length = 10; // we just check the first then characters
    return strncmp(plaintext, result, length);

}

int main (void)
{

    // password 12345
    // it took 9 seconds to work out
    char* ciphertext_base64 = (char*) "U2FsdGVkX19q3SzS6GhhzAKgK/YhFVTkM3RLVxxZ+nM6yXdfLZtvhyRR4oGohDotiifnR1iKyitSpiBM3hng+eoFfGbtgCu3Zh9DwIhgfS5A+OTl5a4L7pRFG4yL432HsMGRC1hy1RNPSzA0U5YyWA==\n";

    char* plaintext = "This is the top seret message in parallel computing! Please keep it in a safe place.";
    char dict[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // changed

    int decryptedtext_len, ciphertext_len, dict_len;

    // cipher (binary) pointer and length
    size_t cipher_len; // size_t is sizeof(type)
    unsigned char* ciphertext;

    unsigned char salt[8];

    ERR_load_crypto_strings();

    Base64Decode(ciphertext_base64, &ciphertext, &cipher_len);

    unsigned char key[16];
    unsigned char iv[16];

    unsigned char plainpassword[] = "00000";
    unsigned char* password = &plainpassword[0];

    // retrive the slater from ciphertext (binary)
    if (strncmp((const char*)ciphertext,"Salted__",8) == 0) { // find the keyword "Salted__"

        memcpy(salt,&ciphertext[8],8);
        ciphertext += 16;
        cipher_len -= 16;

    }

    dict_len = strlen(dict);

    time_t begin = time(NULL);


    for(int i=0; i<dict_len; i++)
        for(int j=0; j<dict_len; j++)
            for(int k=0; k<dict_len; k++)
                for(int l=0; l<dict_len; l++)
                    for(int m=0; m<dict_len; m++){
                        *password = dict[i];
                        *(password+1) = dict[j];
                        *(password+2) = dict[k];
                        *(password+3) = dict[l];
                        *(password+4) = dict[m];


                        initAES(password, salt, key, iv);
                        unsigned char* result = decrypt(ciphertext, cipher_len, key, iv);

                        if (success == 1){
                            if(checkPlaintext(plaintext, result)==0){

                                printf("Password is %s\n", password);

                                time_t end = time(NULL);
                                printf("Time elpased is %ld seconds", (end - begin));

                                return 0;
                            }

                        }

                        free(result);
                    }


    // Clean up

    EVP_cleanup();
    ERR_free_strings();


    return 0;
}

这是并行版本:

int main (void)
{

    // password 12345
    // it took 9 seconds to work out
    char* ciphertext_base64 = (char*) "U2FsdGVkX19q3SzS6GhhzAKgK/YhFVTkM3RLVxxZ+nM6yXdfLZtvhyRR4oGohDotiifnR1iKyitSpiBM3hng+eoFfGbtgCu3Zh9DwIhgfS5A+OTl5a4L7pRFG4yL432HsMGRC1hy1RNPSzA0U5YyWA==\n";

    char* plaintext = "This is the top seret message in parallel computing! Please keep it in a safe place.";
    char dict[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // changed

    int decryptedtext_len, ciphertext_len, dict_len;

    // cipher (binary) pointer and length
    size_t cipher_len; // size_t is sizeof(type)
    unsigned char* ciphertext;

    unsigned char salt[8];

    ERR_load_crypto_strings();

    Base64Decode(ciphertext_base64, &ciphertext, &cipher_len);

    unsigned char key[16];
    unsigned char iv[16];

    omp_set_num_threads(NUM_THREADS);

    double begin = omp_get_wtime();

    
    // retrive the slater from ciphertext (binary)
    if (strncmp((const char*)ciphertext,"Salted__",8) == 0)
    { // find the keyword "Salted__"

        memcpy(salt,&ciphertext[8],8);
        ciphertext += 16;
        cipher_len -= 16;

    }

    dict_len = strlen(dict);

    #pragma omp parallel for shared (key, iv)

    for (int i=0; i<dict_len; i++) 
    {
     unsigned char password[5] = {i};

        for(int j=0; j<dict_len; j++)
        {
         password[1] = dict[j];
            for(int k=0; k<dict_len; k++)
            {
                password[2] = dict[k];
                for(int l=0; l<dict_len; l++)
                {
                  password[3] = dict[l];
                    for(int m=0; m<dict_len; m++)
                    {
                      password[4] = dict[m];
                        {
                            *password = dict[i];
                            *(password+1) = dict[j];
                            *(password+2) = dict[k];
                            *(password+3) = dict[l];
                            *(password+4) = dict[m];


                            initAES(password, salt, key, iv);
                            unsigned char* result = decrypt(ciphertext, cipher_len, key, iv);

                                #pragma omp if(checkPlaintext(plaintext, result)==0)
                                {
                                    
                                    printf("\nPassword is %s\n\n", password);
                                    success == 1;
                                    strcpy(result,password); // Copy thread-private copy to shared variable
                                    time_t end = time(NULL);
                                    printf("\nTime elpased is %ld seconds\n", (end - begin));

                                    exit(0);

                                }

                            free(result);
                        }
                    }
                }
            }
          #pragma omp cancellation point for
        }
    }

// Clean up
EVP_cleanup();
ERR_free_strings();
return 0;
}

应该带上我预期的密码,但是当我运行代码时它不会输出预期的结果. 请帮忙。

标签: copensslopenmp

解决方案


您只有一个用于试用密码 ( plainpassword) 的缓冲区,并且您的所有线程parallel for都试图同时使用它(通过指针password)。这里有大量的数据竞争,由此产生的行为是不确定的。

一种解决方案是在并行循环内部而不是外部声明缓冲区,因为并行区域的本地变量自动是私有的——每个线程都有自己的。

    #pragma omp parallel for shared (key, iv)
    for (int i=0; i<dict_len; i++) {
        unsigned char password[5] = { i };

        for (int j=0; j<dict_len; j++) {
            password[1] = dict[j];

            for (int k=0; k<dict_len; k++) {
                password[2] = dict[k];

                for (int l=0; l<dict_len; l++) {
                    password[3] = dict[l];

                    for (int m=0; m<dict_len; m++) {
                        password[4] = dict[m];

                        // ...

                    }
                }
            }
        }
    }

另请注意

  • plainpassword在您的原始代码中,同时拥有和 并没有明显的好处password。上面只使用了一个数组,而不是一个单独的指针,它为此选择了名称“ password”。
  • *(x + y),其中x和是初选,与(and )y的含义完全相同。下标形式更容易阅读,而且几乎总是使它在风格上更好。x[y]y[x]

更新:

我还观察到并行版本中的这段代码没有意义,尤其是与串行代码相比:

    #pragma omp parallel if (strncmp((const char*)ciphertext,"Salted__",8) == 0) shared(ciphertext, plaintext, ciphertext_base64) private(salt)
    { // find the keyword "Salted__"

        memcpy(salt,&ciphertext[8],8);
        ciphertext += 16;
        cipher_len -= 16;

    }

原始代码每次运行执行if一次语句,每次运行最多执行一次它的主体,所以如果并行版本多次执行主体,并且有副作用(确实如此),那么程序的结果会有所不同. 这部分应该恢复原始代码。


推荐阅读