【问题标题】:The Serial Version and The Parallel Result Not Showing the same output串行版本和并行结果不显示相同的输出
【发布时间】:2021-04-23 22:16:47
【问题描述】:

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

串行工作代码:

#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;
}

应该带上我预期的密码,但是当我运行代码时,它没有输出预期的。请帮忙。

【问题讨论】:

    标签: c openssl openmp


    【解决方案1】:

    您只有一个用于试用密码 (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];
    
                            // ...
    
                        }
                    }
                }
            }
        }
    
    

    还要注意

    • 在原始代码中同时拥有plainpasswordpassword 并没有明显的好处。上面只使用了一个数组,而不是一个单独的指针,它为此选择了名称“password”。
    • *(x + y),其中xy 是原色,与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语句,并且每次运行最多执行一次它的主体,所以如果并行版本多次执行主体,并且它有副作用(确实如此),那么结果的程序会有所不同。这部分应该恢复原代码。

    【讨论】:

    • 非常感谢您的指导,我已经调整了并行代码以尝试遵循您的观点(尽管我将密码设为私有而不是 for 循环内的声明,因为使用它向我展示了未声明的变量错误)。我评论了普通密码,然后在其他条件下工作。现在明显的区别是代码在运行后退出(与之前我必须手动关闭编译器不同)。请帮我看看我做错了什么。
    • 您好约翰先生,当我完全按照您的指导进行操作时,我一直看到以下面粘贴的内容开头的错误。请多多指教先生:In function 'main': anotherparallel.c:163:9: error: expected expression before 'unsigned' 163 | unsigned char password[5] = { i }; | ^~~~~~~~ anotherparallel.c:165:13: error: 'password' undeclared (first use in this function) 165 | password[1] = dict[j];
    • @AdeoluTemitopeOlofintuyi,您报告的编译错误很可能与您的某些循环体周围缺少大括号有关。我复制了您原始代码的该功能,并且没有足够注意识别问题。我已经更正了这个答案中的代码。顺便说一句,这是一个很好的例子,说明为什么在风格上,您应该始终fordowhile 循环以及 ifelse 语句,即使大括号只包含一个语句。
    • 哇!!非常感谢先生,您的快速响应和指导,我将测试新指南并尽快回复您。再次非常感谢。
    • 您好约翰先生,我已经更新了代码(我仔细按照您的指导先生),但我没有看到任何结果,甚至确认密码是否与串行版本匹配。请先生看看这个。感谢期待。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-18
    • 1970-01-01
    相关资源
    最近更新 更多