4 加密解密

4.1 【必须】不得明文存储用户密码等敏感数据

用户密码应该使用 Argon2, scrypt, bcrypt, pbkdf2 等算法做哈希之后再存入存储系统, https://password-hashing.net/

https://libsodium.gitbook.io/doc/password_hashing/default_phf#example-2-password-storage

用户敏感数据,应该做到传输过程中加密,存储状态下加密 传输过程中加密,可以使用 HTTPS 等认证加密通信协议

存储状态下加密,可以使用 SQLCipher 等类似方案。

4.2 【必须】内存中的用户密码等敏感数据应该安全抹除

例如用户密码等,即使是临时使用,也应在使用完成后应当将内容彻底清空。

错误:

  1. #include <openssl/crypto.h>
  2. #include <unistd.h>
  3. {
  4. ...
  5. string user_password(100, '\0');
  6. snprintf(&user_password, "password: %s", user_password.size(), password_from_input);
  7. ...
  8. }

正确:

  1. {
  2. ...
  3. string user_password(100, '\0');
  4. snprintf(&user_password, "password: %s", user_password.size(), password_from_input);
  5. ...
  6. OPENSSL_cleanse(&user_password[0], user_password.size());
  7. }

关联漏洞:

高风险-敏感信息泄露

4.3 【必须】rand() 类函数应正确初始化

rand类函数的随机性并不高。而且在使用前需要使用srand()来初始化。未初始化的随机数可能导致某些内容可预测。

  1. // Bad
  2. int main() {
  3. int foo = rand();
  4. return 0;
  5. }

上述代码执行完成后,foo的值是固定的。它等效于 srand(1); rand();

  1. // Good
  2. int main() {
  3. srand(time(0));
  4. int foo = rand();
  5. return 0;
  6. }

关联漏洞:

高风险-逻辑漏洞

4.4 【必须】在需要高强度安全加密时不应使用弱PRNG函数

在需要生成 AES/SM1/HMAC 等算法的密钥/IV/Nonce, RSA/ECDSA/ECDH 等算法的私钥,这类需要高安全性的业务场景,必须使用密码学安全的随机数生成器 (Cryptographically Secure PseudoRandom Number Generator (CSPRNG) ), 不得使用 rand() 等无密码学安全性保证的普通随机数生成器。

推荐使用的 CSPRNG 有:

  1. OpenSSL 中的 RAND_bytes() 函数, https://www.openssl.org/docs/man1.1.1/man3/RAND_bytes.html

  2. libsodium 中的 randombytes_buf() 函数

  3. Linux kernel 的 getrandom() 系统调用, https://man7.org/linux/man-pages/man2/getrandom.2.html . 或者读 /dev/urandom 文件, 或者 /dev/random 文件。

  4. Apple IOS 的 SecRandomCopyBytes(), https://developer.apple.com/documentation/security/1399291-secrandomcopybytes

  5. Windows 下的 BCryptGenRandom(), CryptGenRandom(), RtlGenRandom()

  1. #include <openssl/aes.h>
  2. #include <openssl/crypto.h>
  3. #include <openssl/rand.h>
  4. #include <unistd.h>
  5. {
  6. unsigned char key[16];
  7. if (1 != RAND_bytes(&key[0], sizeof(key))) { //... 错误处理
  8. return -1;
  9. }
  10. AES_KEY aes_key;
  11. if (0 != AES_set_encrypt_key(&key[0], sizeof(key) * 8, &aes_key)) {
  12. // ... 错误处理
  13. return -1;
  14. }
  15. ...
  16. OPENSSL_cleanse(&key[0], sizeof(key));
  17. }

rand()类函数的随机性并不高。敏感操作时,如设计加密算法时,不得使用rand()或者类似的简单线性同余伪随机数生成器来作为随机数发生器。符合该定义的比特序列的特点是,序列中“1”的数量约等于“0”的数量;同理,“01”、“00”、“10”、“11”的数量大致相同,以此类推。

例如 C 标准库中的 rand() 的实现只是简单的线性同余算法,生成的伪随机数具有较强的可预测性。

当需要实现高强度加密,例如涉及通信安全时,不应当使用 rand() 作为随机数发生器。

实际应用中, C++11 标准提供的random_device保证加密的安全性和随机性 但是 C++ 标准并不保证这一点。跨平台的代码可以考虑用 OpenSSL 等保证密码学安全的库里的随机数发生器。

关联漏洞:

高风险-敏感数据泄露

4.5 【必须】自己实现的rand范围不应过小

如果在弱安全场景相关的算法中自己实现了PRNG,请确保rand出来的随机数不会很小或可预测。

  1. // Bad
  2. int32_t val = ((state[0] * 1103515245U) + 12345U) & 999999;

上述例子可能想生成0~999999共100万种可能的随机数,但是999999的二进制是11110100001000111111,与&运算后,0位一直是0,所以生成出的范围明显会小于100万种。

  1. // Good
  2. int32_t val = ((state[0] * 1103515245U) + 12345U) % 1000000;
  3. // Good
  4. int32_t val = ((state[0] * 1103515245U) + 12345U) & 0x7fffffff;

关联漏洞:

高风险-逻辑漏洞