Cryptography Lessons: Common Pitfalls and Best Practices
Quoted from stackexchange
Encryption is the first line of defense for information security, but it is also where mistakes are most easily made. Even experienced engineers can stumble in applied cryptography. A single key reuse, an improper IV, or choosing the wrong encryption mode can lead to the entire system being compromised. These lessons come from real vulnerabilities and deserve a closer look.
Don’t invent algorithms
Don’t invent your own encryption algorithms or protocols, because mistakes are easy to make. Cryptographic algorithms are complex and require extensive, rigorous review to ensure their security. Self-invented algorithms or protocols usually cannot provide sound mathematical reasoning to prove their security.
The best choice is to use standard cryptographic algorithms and protocols. A standard algorithm is usually born to solve certain past problems, and those problems are very likely the ones you are facing. For example:
- Use TLS or SSL to solve communication security
- Use GPG or PGP to solve data-at-rest security
These are well-vetted cryptographic schemes. If you need to use cryptographic interfaces to implement certain functionality, prefer high-level crypto libraries such as cryptlib, GPGME, Keyczar, etc., rather than low-level libraries like OpenSSL, CryptoAPI, JCE, which are error-prone and difficult to apply correctly.
Don’t use unauthenticated encryption
This is a very common mistake: encrypting data without authenticating it.
For example, a developer wants to protect sensitive information and encrypts the message using AES-CBC mode.
This only achieves message confidentiality, but cannot guarantee security against active attacks such as tampering, replay attacks, and reflection attacks.
Adding message authentication solves this problem.
Many online systems have suffered serious vulnerabilities due to this issue, such as ASP. NET, XML encryption, Amazon EC2, JavaServer Faces, Ruby on Rails, OWASP ESAPI, IPSEC, WEP, ASP. NET again, and SSH2.
To avoid this problem, message authentication should be used with every encryption. There are two mainstream approaches:
- The simplest way is to use an off-the-shelf authenticated encryption scheme such as GCM, CWC, EAX, CCM, or OCB. These methods encrypt and authenticate data simultaneously, so the user no longer needs to worry about this issue.
- Another approach is to implement message authentication yourself. First, encrypt the message with an appropriate symmetric encryption method (e.g., AES-CBC), then generate a message authentication code (such as AES-CMAC, SHA1-HMAC, or SHA256-HMAC) over the ciphertext, and append the generated MAC digest to the ciphertext before transmitting the data. On the receiving end, verify the validity of the MAC digest before decrypting the data.
graph LR
A[明文] -->|加密| B[AES-CBC]
B --> C[密文]
C -->|计算 MAC| D[HMAC]
D --> E[密文 + MAC]
F[接收端] -->|验证 MAC| G[HMAC 验证]
G -->|通过| H[解密]
G -->|失败| I[丢弃数据]
GCM mode is recommended because it automatically provides authentication during encryption, avoiding the complexity of manually implementing a MAC.
Do not use the same key for encryption, authentication, or signing
A key should not be reused for multiple purposes; doing so expands the risk.
For example, if you have an RSA key pair, you should not use it for both encryption and signing. If you need both encryption and signing, generate two key pairs.
Similarly, for symmetric encryption, you should not use the encryption key to generate message authentication codes. In short, do not reuse keys for different purposes.
Be careful when hashing concatenated strings
For example, a developer wants to obtain a hash of strings S and T. They concatenate S and T and hash the result, obtaining H(S||T). This is flawed.
The problem is that concatenation makes the boundary between the two strings ambiguous. For example, builtin||securely = built||insecurely, the hash operation cannot distinguish string S from string T. As a result, an attacker may be able to change the boundary between the two strings without changing the hash value. For example, if Alice wants to send two strings builtin and securely, an attacker could change the strings to built and insecurely without invalidating the hash value.
Similar problems arise when performing digital signatures or message authentication code operations on concatenated strings.
The solution is not to concatenate plaintext directly, but to use an encoding that can be decoded unambiguously. For example, when you want to compute H(S||T), consider using H(length(S)||S||T) instead, where length(S) is a 32-bit value representing the length of the string S; alternatively, use approaches such as H(H(S)||H(T)) or H(H(S)||T).
For related cases on this issue, see this flaw in Amazon Web Services, this flaw in Flickr.
Do not reuse nonces or IVs
Many encryption modes require an IV (initialization vector). Never reuse the same IV, as this can lead to serious vulnerabilities.
graph LR
subgraph 错误示例: IV 重用
A1[明文 A] -->|IV=123| B1[加密]
A2[明文 B] -->|IV=123| B2[加密]
B1 --> C1[密文 A]
B2 --> C2[密文 B]
C1 -.-> D[攻击者 XOR]
C2 -.-> D
D --> E[获得明文 B]
end
- For stream ciphers, such as CTR or OFB mode, this can allow encrypted data to be easily recovered as plaintext
- For other modes such as CBC, reusing an IV can also lead to plaintext recovery attacks
Therefore, regardless of which encryption mode you use, you should not reuse an IV.
Generate a new random IV for every encryption, and do the same for the nonce in GCM mode.
Ensure the random number generator has sufficient entropy
Make sure to use a cryptographically secure pseudorandom number generator for producing keys, IVs, nonces, and so on, rather than using rand(), random(), drand48() and the like.
Ensure the random number generator is seeded with enough entropy. Do not use dates as seeds, since they are predictable.
For example: srand(time(NULL)) is not good. A better approach is to seed the random number generator with 128 bits of data or true random numbers, for example through /dev/urandom, CryptGenRandom or similar tools. In Java, use SecureRandom instead of Random. In . NET, use System. Security. Cryptography. RandomNumberGenerator instead of System. Random. In Python, use random. SystemRandom instead of random.
For symmetric encryption, do not use ECB mode
For example, the plaintext of an image is shown below:

The result after encrypting this data with ECB mode:

The result after encrypting this data with CBC mode:

Avoid using passwords as keys
A common mistake is to use a password or its hash as the encryption key. Keys need sufficient entropy, which most passwords and passphrases lack, making them vulnerable to dictionary attacks.
If you must derive a key from a password, you can increase the difficulty of dictionary attacks through iterative hashing:
// ❌ 错误:直接用密码哈希作为密钥
const badKey = sha256(password);
// ✅ 正确:使用 PBKDF2 硬化
const crypto = require('crypto');
const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
// ✅ 正确:使用 bcrypt(更推荐)
const bcrypt = require('bcrypt');
const hashedKey = await bcrypt.hash(password, 12); // 12 是工作因子
It is recommended to use Argon2id (a winner of the Password Hashing Competition) or bcrypt for password hardening, as these algorithms offer better defense against hardware-accelerated attacks.
Do not use insecure key lengths
Ensure you use sufficiently long keys. For example, 1024-bit RSA keys were proven insecure around 2010; it is recommended to use RSA keys of at least 2048 bits.
For symmetric ciphers, keys of 128 bits or more should be used.
Summary
| Principle | Risk | Best Practice |
|---|---|---|
| Don’t invent algorithms | Security cannot be proven | Use standard algorithms (TLS, GPG, AES) |
| Use authenticated encryption | Data can be tampered with | Use authenticated encryption modes like GCM, EAX |
| Key separation | High risk from a single key compromise | Use separate keys for encryption, signing, and authentication |
| Concatenate hashes correctly | Ambiguous boundaries can be exploited | Use length prefixes or nested hashing |
| Don’t reuse IV/Nonce | Plaintext can be recovered | Generate a random IV for each encryption |
| Use secure random numbers | Keys can be predicted | Use a CSPRNG (e.g., /dev/urandom) |
| Avoid ECB mode | Mode reveals plaintext structure | Use modes like CBC, GCM |
| Avoid using passwords directly as keys | Insufficient entropy makes them easy to crack | Use PBKDF2, bcrypt for hardening |
| Use sufficient key lengths | Risk of brute-force attacks | RSA ≥2048-bit, symmetric ≥128-bit |
The pitfalls of cryptography often hide in the details. Using mature libraries, following best practices, and understanding common vulnerabilities are key to protecting system security.