Securing symmetric encryption algorithms in Java
2023年10月18日
0 分で読めますIn our connected world, securing digital data has become an utmost priority. With the wide spread of Java applications in various sectors, from banking to healthcare, we must emphasize the importance of encryption. Encryption is converting readable data or plaintext into unreadable data or ciphertext, ensuring that even if encrypted data is intercepted, it remains inaccessible to unauthorized individuals.
The first choice you must make as a developer is whether you need encryption. Although this sounds like a strange question, the key principle of encryption is that the ciphertext can be reverted into the original text. When looking at passwords, for instance, we do not want encryption as we do not want to be able to “decrypt” the original text. Therefore, we rely on hashing for passwords and not encryption.
Encryption in Java
Java applications often handle sensitive data, including customer information, financial details, and transaction records. As a Java developer, employing robust encryption algorithms is vital to maintaining the integrity and confidentiality of sensitive data. This protects your users and any essential data you have inside your system.
Symmetric vs asymmetric encryption
Generally speaking, we can distinguish two types of encryption: symmetric and asymmetric. Either type works fundamentally differently, and either type has different use cases.
Symmetric encryption uses the same key for both encryption and decryption, making it faster but less secure when the key needs to be shared. On the other hand, asymmetric encryption uses a pair of keys (public and private). The public key is used for encryption, and the private key is for decryption. While symmetric encryption is suited for scenarios where large volumes of data need to be encrypted, asymmetric encryption is ideal for securely sharing keys over a network. Although we distinguish between two types of encryption, in this article, we will only focus on symmetric encryption in Java applications.
When using symmetric encryption, it's best to rely on services that leverage hardware security modules (HSMs) for key management and envelope encryption patterns in order to simplify key management, which is the hardest part of the encryption solution. However, if you're unable to use a service like Amazon Key Management Service (KMS) and still want to do this, the information below will show you how to do so securely. In the remainder of this article, we focus on implementing symmetric encryption in your Java application using the javax.crypto
packages.
Understanding outdated encryption algorithms and their risks
Despite the importance of encryption, not all encryption algorithms are created equal. Some older or "outdated" encryption algorithms, such as DES (Data Encryption Standard) or 3DES, have been found to have vulnerabilities that attackers can exploit. These algorithms were once considered secure, but advances in computing power and cryptanalysis techniques have rendered them unsafe.
Using insecure encryption algorithms in your Java applications can lead to potential risks. An attacker who gains access to your encrypted data may be able to decrypt it if the encryption algorithm is outdated and has known vulnerabilities. This could result in a data breach, with potentially devastating consequences for your users and your organization.
Recommendations for algorithms and cipher modes
If we look at symmetric encryption algorithms, the OWASP foundation currently advises AES (Advanced Encryption Standard) with a key that is at least 128 bits but preferably 256 bits. In addition, you should use the algorithm with a secure mode. Also, the NIST cryptographic standards and guidelines approve AES encryption as a block cipher technique.
Multiple modes are available with a cipher like AES. These modes have different security and performance characteristics. When it comes to modes, avoid ECB (Electronic Codebook) mode, which does not provide serious message confidentiality. Instead, use modes like CCM (Counter with CBC-MAC) or GCM (Galois/Counter Mode). Both of these modes are authenticated modes and guarantee the data's integrity, confidentiality, and authenticity.
Identifying weak encryption algorithms in Java
It is relatively easy to use the javax.crypto
APIs for implementing a small encryption service in Java. Below is an example of EncryptionService
in Java based on the outdated DES algorithm using the ECB mode.
1public class EncryptionServiceDes {
2
3 private SecretKey secretKey;
4 private Cipher cipher;
5
6 public EncryptionServiceDes(String key) throws GeneralSecurityException {
7 byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
8 DESKeySpec desKeySpec = new DESKeySpec(keyBytes);
9 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
10 secretKey = keyFactory.generateSecret(desKeySpec);
11 cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
12 }
13
14 public String encrypt(String original) throws GeneralSecurityException {
15 cipher.init(Cipher.ENCRYPT_MODE, secretKey);
16 // Encrypt the original data
17 byte[] encryptedData = cipher.doFinal(original.getBytes(StandardCharsets.UTF_8));
18 // Encode the encrypted data in base64 for better handling
19 return Base64.getEncoder().encodeToString(encryptedData);
20 }
21 public String decrypt(String cypher) throws GeneralSecurityException{
22 cipher.init(Cipher.DECRYPT_MODE, secretKey);
23 // Decode the base64-encoded ciphertext
24 byte[] encryptedData = Base64.getDecoder().decode(cypher);
25 // Decrypt the data
26 byte[] decryptedData = cipher.doFinal(encryptedData);
27 return new String(decryptedData, StandardCharsets.UTF_8);
28 }
29}
Now that we've built a known insecure encryption algorithm into a project, we'll use the free Snyk service to detect this insecure use and guide us to a better solution.
First, sign up for a free Snyk account. You can easily get started using your GitHub, Bitbucket, Azure AD, or Docker account.
I then connected my GitHub repository to Snyk. Snyk Code’s static analysis immediately warns me that my application uses a Broken or Risky Cryptographic Algorithm. This is spot on since I have multiple references to DES in the service above. The Snyk Code engine also advises me to consider using AES, which aligns with the advice from OWASP.
Despite using AES in the short snippet below, I am using the CBC mode which is not considered secure.
1…
2 public EncryptionServiceAES(String key) throws GeneralSecurityException {
3 byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
4 secretKey = new SecretKeySpec(keyBytes, "AES");
5 cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
6 }
7…
Snyk will also identify the use of insecure modes, among other things. The screenshot below is taken from the Snyk plugin that scans my code inside of InteliJ IDEA and provides useful information, like external fix examples.
Changing the EncryptionService
to a version that uses AES/GCM/NoPadding
, like below, is a far better solution than I had before and is in line with the current advice that OWASP provides.
1public class EncryptionServiceAESGCM {
2 private SecretKey secretKey;
3 private Cipher cipher;
4
5 public EncryptionServiceAESGCM(String key) throws GeneralSecurityException {
6 byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
7 secretKey = new SecretKeySpec(keyBytes, "AES");
8 cipher = Cipher.getInstance("AES/GCM/NoPadding");
9 }
10
11 public String encrypt(String original) throws GeneralSecurityException{
12 byte[] iv = new byte[16]; // Initialization Vector
13 SecureRandom random = new SecureRandom();
14 random.nextBytes(iv); // Generate a random IV
15 cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, iv));
16 byte[] encryptedData = cipher.doFinal(original.getBytes());
17 var encoder = Base64.getEncoder();
18 var encrypt64 = encoder.encode(encryptedData);
19 var iv64 = encoder.encode(iv);
20 return new String(encrypt64) + "#" + new String(iv64);
21 }
22
23 public String decrypt(String cypher) throws GeneralSecurityException{
24 var split = cypher.split("#");
25 var decoder = Base64.getDecoder();
26 var cypherText = decoder.decode(split[0]);
27 var iv = decoder.decode(split[1]);
28 var paraSpec = new GCMParameterSpec(128, iv);
29 cipher.init(Cipher.DECRYPT_MODE, secretKey, paraSpec);
30 byte[] decryptedData = cipher.doFinal(cypherText);
31 return new String(decryptedData);
32 }
33}
Continuously reviewing encryption algorithms in your Java applications
While updating outdated encryption algorithms is crucial, it's equally important to monitor your codebase for new security issues continuously. The encryption algorithms considered safe and secure at the moment of writing will most definitely be out of date in a matter of years.
Proactively scanning and identifying this should, therefore, be a continuous exercise. This does not only count for encryption algorithms but obviously also for other issues in the code you maintain. Adopting and using a static analysis security testing (SAST) tool like Snyk code will help you easily recognize, identify, and mitigate these issues before actual harm is done.
Snyk Code for Java can help you for free
Snyk Code is a state-of-the-art static application security testing (SAST) tool that can identify security vulnerabilities within your codebase. It supports several programming languages, including Java, by analyzing code and providing real-time feedback to developers. Snyk Code can be integrated into your CI/CD pipeline to automatically scan your code with each commit, helping you catch and fix security issues early in the development cycle. Additionally, you can connect it to your git repository, use it on the command line using our CLI, and integrate it with all the popular IDEs for Java.
This can catch problems in your code early and often in every step of the development lifecycle. Most importantly, you can use it for free by signing up for Snyk below.
The code examples used in this blog post are published on GitHub, so you can experiment with it yourself. If you want to have more good resources around security for Java developers. Take a look at: