当我们使用openssl enc 命令工具进行加密的时候,涉及到一个参数 即salt,下面简单分析一下salt参数。salt说白了就是一个随机数,salt与passwd串联,然后计算其hash值来防御dictionary attacks 和预计算的rainbow table 攻击。在openssl 的enc 命令中,通过salt 与passwd 来生加密(解密)密钥和初始向量IV。
一,openssl中enc 中salt相关参数
需要注意的就是,当仅设置-K时,还需要设置IV。
-pass arg the password source. For more information about the format of arg see the PASS PHRASE ARGUMENTS section in openssl(1). -salt use a salt in the key derivation routines. This is the default. -nosalt don't use a salt in the key derivation routines. This option SHOULD NOT be used except for test purposes or compatibility with ancient versions of OpenSSL and SSLeay. -k password the password to derive the key from. This is for compatibility with previous versions of OpenSSL. Superseded by the -pass argument. -kfile filename read the password to derive the key from the first line of filename. This is for compatibility with previous versions of OpenSSL. Superseded by the -pass argument. -nosalt do not use a salt -salt use salt (randomly generated or provide with -S option) when encrypting (this is the default). -S salt the actual salt to use: this must be represented as a string of hex digits. -K key the actual key to use: this must be represented as a string comprised only of hex digits. If only the key is specified, the IV must additionally specified using the -iv option. When both a key and a password are specified, the key given with the -K option will be used and the IV generated from the password will be taken. It probably does not make much sense to specify both key and password. -iv IV the actual IV to use: this must be represented as a string comprised only of hex digits. When only the key is specified using the -K option, the IV must explicitly be defined. When a password is being specified using one of the other options, the IV is generated from this password.
二,openssl中enc.c 文件分析
enc.c文件是openssl中enc命令对应的源码,我们专注于salt相关参数,故只摘录部分代码。
首先我们来看一下,openssl中调用 EVP_BytesToKey通过passwd和salt来生成Key 和IV,在enc.c 中有如下代码
//line 555 Opensl1.0.1c EVP_BytesToKey(cipher,dgst,sptr,(unsigned char *)str, strlen(str),1,key,iv); //函数原型 //line 115 Evp_key.c Openssl1.0.1c int EVP_BytesToKey(const EVP_CIPHER *type, const EVP_MD *md, const unsigned char *salt, const unsigned char *data, int datal, int count, unsigned char *key, unsigned char *iv)
通过函数原型,除了str 参数,其他参数我们非常容易看清其意思。我们向前查找,发现各个参数的意思以及具体如何定义的。
1,dgst 为MD5
//line 340~343 Openssl1.0.1c //摘要算法使用MD5 if (dgst == NULL) { dgst = EVP_md5();
2,sptr 为salt
//line513~ 553 if (str != NULL) { /* Salt handling: if encrypting generate a salt and * write to output BIO. If decrypting read salt from * input BIO. */ unsigned char *sptr; if(nosalt) sptr = NULL;//如果参数制定nosalt 则sptr为NULL else { if(enc) { /**line 270~274 用户指定salt值 **else if (strcmp(*argv,"-S") == 0) **{ ** if (--argc < 1) goto bad; ** hsalt= *(++argv); **} **/ if(hsalt) { if(!set_hex(hsalt,salt,sizeof salt)) { BIO_printf(bio_err,"invalid hex salt value\n"); goto end; } } else if (RAND_pseudo_bytes(salt, sizeof salt) < 0) goto end; /* If -P option then don't bother writing */ if((printkey != 2)&& (BIO_write(wbio,magic, sizeof magic-1) != sizeof magic-1 || BIO_write(wbio,(char *)salt,sizeof salt) != sizeof salt)) { BIO_printf(bio_err,"error writing output file\n"); goto end; } } else if(BIO_read(rbio,mbuf,sizeof mbuf) != sizeof mbuf || BIO_read(rbio,(unsigned char *)salt,sizeof salt) != sizeof salt){ BIO_printf(bio_err,"error reading input file\n"); goto end; } else if(memcmp(mbuf,magic,sizeof magic-1)) { BIO_printf(bio_err,"bad magic number\n"); goto end; } sptr = salt; }
3,str为passwd
//line 180~184 Openssl1.0.1c else if (strcmp(*argv,"-pass") == 0) // passwd 参数,新版本,替代-k { if (--argc < 1) goto bad; passarg= *(++argv); }//line 223~227 Openssl1.0.1celse if (strcmp(*argv,"-k") == 0)// passwd 参数,旧版本,建议使用-pass参数 { if (--argc < 1) goto bad; str= *(++argv); } //line 414~421 Openssl1.0.1c if(!str && passarg) { if(!app_passwd(bio_err, passarg, NULL, &pass, NULL)) { BIO_printf(bio_err, "Error getting password\n"); goto end; } str = pass; }三,EVP_BytesToKey 函数
EVP_BytesToKey函数的功能就是生产key和iv。
int EVP_BytesToKey(const EVP_CIPHER *type, const EVP_MD *md, const unsigned char *salt, const unsigned char *data, int datal, int count, unsigned char *key, unsigned char *iv) { EVP_MD_CTX c; unsigned char md_buf[EVP_MAX_MD_SIZE]; int niv,nkey,addmd=0; unsigned int mds=0,i; int rv = 0; nkey=type->key_len; niv=type->iv_len; OPENSSL_assert(nkey <= EVP_MAX_KEY_LENGTH); OPENSSL_assert(niv <= EVP_MAX_IV_LENGTH); if (data == NULL) return(nkey); EVP_MD_CTX_init(&c); for (;;) { if (!EVP_DigestInit_ex(&c,md, NULL)) return 0; if (addmd++) if (!EVP_DigestUpdate(&c,&(md_buf[0]),mds)) goto err; // 将passwd和salt串联进行hash if (!EVP_DigestUpdate(&c,data,datal)) goto err; if (salt != NULL) if (!EVP_DigestUpdate(&c,salt,PKCS5_SALT_LEN)) goto err; if (!EVP_DigestFinal_ex(&c,&(md_buf[0]),&mds)) goto err; for (i=1; i<(unsigned int)count; i++)// 对hash结果再进行count次hash { if (!EVP_DigestInit_ex(&c,md, NULL)) goto err; if (!EVP_DigestUpdate(&c,&(md_buf[0]),mds)) goto err; if (!EVP_DigestFinal_ex(&c,&(md_buf[0]),&mds)) goto err; } i=0; if (nkey)//设置key { for (;;) { if (nkey == 0) break; if (i == mds) break; if (key != NULL) *(key++)=md_buf[i]; nkey--; i++; } } if (niv && (i != mds))//设置IV { for (;;) { if (niv == 0) break; if (i == mds) break; if (iv != NULL) *(iv++)=md_buf[i]; niv--; i++; } } if ((nkey == 0) && (niv == 0)) break; } rv = type->key_len; err: EVP_MD_CTX_cleanup(&c); OPENSSL_cleanse(&(md_buf[0]),EVP_MAX_MD_SIZE); return rv; }