JVM Calendar: JDPR or Java Data Protection Recommendations
After GDPR, we offer some additional thoughts on data protection within Java applications.
Join the DZone community and get the full member experience.
Join For FreeOne major event that reverberated across the entire tech ecosystem was the 2018 introduction of GDPR, the General Data Protection Regulation for European Citizens. While it remains to be seen I this will or will not impact British citizens, it offers a positive opportunity for Java developers to take interest in three areas of security:
- Locating personally-identifiable information (PII) or other sensitive data in their applications.
- Using cryptography correctly, to properly protect this information and secure systems.
- Applying effective patch management of custom code, libraries, and JREs.
Locating PII and Sensitive Data
Java developers have a benefit of locating PII through static typing and clear APIs. As an example for developers writing POJOs, standardized method naming adopted by most of the community can reveal potential PII information like getAddress()
, getName()
, or getSomethingElseThatLooksLikePII()
. Combing through method names, developers with moderate familiarity with PII goals can easily locate points of interest where PII bears sleep and identify potential leaks.
Java APIs also provide another easy method to identify PII bear caves. For instance, most persistence is handled by JDBC or ORM libraries like Hibernate. By watching the queries or looking at the databases directly, developers can identify PII flowing over these interfaces. The presence of standardized APIs, like JDBC and Hibernate, make it easy work for instrumentation-based tools to locate PII similar to the way that free instrumentation-based tools monitor for security or performance.
Using Cryptography Correctly
Java developers benefit from the Java Cryptographic Architecture, a suite of compatible tools and libraries that cover basic cryptographic methods. Since JDK 9, most JREs have shipped with Unlimited Strength Cryptography. Limitations on cryptographic strength and configuration of policy files for unlimited strength have been a source of confusion for development projects that require strong cryptography. Java developers provide a quick test you can run to understand if unlimited strength is configured correctly.
import javax.crypto.Cipher;
class TestCryptoLimitations {
public static void main(String[] args) {
try {
System.out.println("Hello World!");
int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
System.out.println(“Max key length is “ + maxKeyLen);
} catch (Exception e){
System.out.println("Sad world :(");
}
}
}
Moving on, there a three major cryptography use cases your likely to encounter in your projects: hashing files/credentials, symmetric cryptography, and asymmetric cryptography.
Hashing and Message Digest
Hashing is commonly used to produce file checksums or encrypt credentials. It is a one-way algorithm, computing the hash value from a key is can be accomplished in polynomial time and is considered efficient to compute. The converse operation, finding a value to produce a given hash, know as a collision, is not efficient and computationally expensive. Consider the Birthday Attack takes O(2^(n/2) time as an example. The behavior of one-way hash functions makes them suitable for credential storage or testing for data (e.g., file) tampering. Even a small change to a file will significantly change the hash value.
Common hashing algorithms are: SHA-256, SHA-512, or older ones such as MD5 and SHA-1 that may be blacklisted in the jre/lib/security/java.security file under the jdk.certpath.disabledAlgorithms property.
When hashing the secret part of a credential (e.g., passwords), the values to hash should include a unique per user salt. Salts are cryptographically random values, which are difficult to guess and usually take the form of a long string of hexadecimal values or byte array. The inclusion of a salt prevents pre-calculation attacks where the attacker just looks up the pre-computed values in a dictionary called a rainbow table. Salting is not required to hash non-secret information, such as a file check. It’s preferable to use key derivation function like PBKDF2 over credential hashing on your own and we will cover that shortly.
Computing the hash value of a given file:
MessageDigest md = MessageDigest.getInstance(“SHA-256”);
md.update(fileinbytes);
final byte[] hashed = md.doFinal();
This can be done using Password Based Key Derivation (PBKDF2) to produce a hash of information that should be secret and harder to precompute.
final String password = "12345";
final String salt = "user@example.com";
final int iterations = 32;
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), iterations, 512);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hashed = skf.generateSecret(keySpec).getEncoded();
final String encoded = Base64.getEncoder().encodeToString(hashed);
System.out.println("Encoded: " + encoded);
Symmetric Ciphers
Symmetric ciphers are used to encrypt and decrypt information. Symmetric ciphers get their name because encryption and decryption operations use the same secret key. Symmetric cryptography is is the backbone of cryptography and is faster than asymmetric. Secret keys are generally long strings or byte arrays and not easily found in dictionaries.
Common symmetric algorithms include AES, Blowfish, and DES. Following is an example of symmetric encryption and decryption.
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
public class CryptoAdvent {
public static String encrypt(byte[] key, byte[] initVector, String value) throws Exception {
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes("UTF-8"));
String encoded = Base64.getEncoder().encodeToString(encrypted);
return encoded;
}
public static String decrypt(byte[] key, byte[] initVector, String encrypted) throws Exception {
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString();
}
public static void main(String[] args) {
try {
// Note: Generates a new Key and initVector each time the program runs. In a real
// implementation you would need to store the key and initVector as secrets
// to later decrypt.
//
SecureRandom sr = new SecureRandom();
byte[] key = new byte[16];
sr.nextBytes(key); // 128 bit key
byte[] initVector = new byte[16];
sr.nextBytes(initVector); // 16 bytes IV
System.out.println( "Random key="+bytesToHex(key) );
System.out.println( "initVector="+bytesToHex(initVector) );
String payload = "This is the plaintext from Erik and Milton's article.";
System.out.println("Original text="+payload);
String encrypted = encrypt( key, initVector, payload );
System.out.println("Encrypted text="+encrypted);
String decrypted = decrypt( key, initVector, encrypted );
System.out.println("Decrypted text="+decrypted);
String result = decrypted.equals(payload) ? "It works!" : "Somethings not right.";
System.out.println(result);
} catch( Exception t ) {
t.printStackTrace();
}
}
}
An example output of this is:
Random key=E5 01 B6 AC 9C A5 6D 64 08 DE AB DD 83 9C E0 87
initVector=AC 09 5C B0 6E 76 3B E6 A4 2B D7 4C B3 4B CE F8
Original text=This is the plaintext from Erik and Milton's article.
Encrypted text=/LQlJp7fR4Gkq5unWU4X+5qrje1WWKyCms+MPzcwsFf2eE+QHVr2RQDoJVfrSmoc/dM5ulrtk5z4z4evozprUQ==
Decrypted text=This is the plaintext from Erik and Milton's article.
It works!
Asymmetric Ciphers
Most Internet cryptography, such as HTTPS, is built around asymmetric cryptography. The advantage of asymmetric cryptography is that it provides a method for client and server to securely negotiate secret keys and cipher suites, which are later used to secure data using common symmetric encryption techniques. Asymmetric encryption makes use of some new classes like KeyPair
, PublicKey
, PrivateKey
, and Certificate
. Each of these classes are designed with the idea that the public key can be shared for access to anyone over untrusted networks, who then encrypts messages using their own private key. The owner of the public key can then decrypt the message using their own private key, enabling the two to talk.
The most common use of asymmetric cryptography is encrypted HTTPS communication with websites. In this case, each client has a list of known Certificate Authorities that establish identity and validate the owner of each website’s Public Key.
Unlike browsers, most JREs have a truncated list of Certificate Authorities that include many different certificates from Digicert but do not contain popular Certificate Authorities such as Amazon Trust (AWS) or SSL.com. As a result, Java clients may fail with PKIX Exceptions about an inability to authenticate certain URLs.
When this occurs, developers should not simply disable SSL authentication. Doing this simply ensures that the application sends information over a secure channel without knowing what is on the other end of this channel.
The proper mechanism is adding the root certificate to the lib/security/cacerts file:
keytool -importcert -keystore lib/security/cacerts -alias awstrust -file awstrust.cer
While it is reasonable to add root certificates to this root certificate authority store, root certificates are not needed for things like headphones, and if they were, the private key for those headphones should remain private.
Effective Patch Management
There are two aspects that developers need to consider for patching:
- Keeping cadence with the JRE
- Keeping up with library vulnerabilities
Both the Oracle JRE and Amazon Corretto patch on a quarterly basis. Unlike the Oracle JRE, whose Java 8 support ends in April 2019, Amazon Corretto has committed to no-cost patch on a quarterly basis until 2023 — an addition of about four years.
Java developers are not immune to the type of attack that recently hit the Node community, stealing various amounts of bitcoin. For example in 2014, an attack was demonstrated against Maven Central to modify bytecode as it moved across the network within a jar file. While the response was quick to enable SSL, most JAR files there are still not signed through the jarsigner tool, making it more difficult to detect tampering. Similar attacks have occurred elsewhere.
When vulnerabilities are discovered in third party libraries, your applications have little defense until patches are deployed. This is why designing software applications, services, and infrastructure for rapid patching is essential for strong security. Beyond rapid deployment, testing is often a common barrier. Most organizations delay patching since they don’t have significant confidence that pushing a patch will not break production. This is why investing in high quality unit tests cases is so important.
To monitor security posture of third-party libraries, free tools are available to developers like OWASP Dependency Check or Contrast Community Edition.
Many software buyers enforce this dependency analysis, called Software Composition Analysis, in part because it is easy to detect by just looking at the libraries. Developers who disregard this recommendation may find their applications undeployed and subsequent invoices unpaid.
General Advice
Developers should pay attention to the types of data that they have and how they protect this data. Due to the regulatory environment of GDPR, the default business approach of “save all data” can become a serious business liability. Data may be the new oil, but oil is flammable, and when handling encrypted data, do not store secret keys with the encrypted data, as that defeats the purpose.
Published at DZone with permission of Erik Costlow. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments