In today’s digital age, financial institutions rely heavily on encryption to protect sensitive data in their banking applications. However, despite the critical role of cryptography, many implementations suffer from fundamental flaws that create a false sense of security. Misconceptions about cryptographic principles, combined with efforts to quickly comply with security standards, often lead to the use of weak or outdated algorithms, improper key management, and flawed encryption protocols. These common mistakes leave banking clients vulnerable to attacks, resulting in severe consequences such as data breaches and financial losses. Understanding and addressing these issues is crucial for developers to ensure robust security in financial software. This blog delves into the prevalent encryption pitfalls in banking applications and highlights best practices to mitigate these risks.
Some of the common pitfalls we will discuss below are:
The idea of encryption in transit means that you want to protect the user’s data during transit in the unforeseen event of a channel breach. In those events, the encrypted user’s data is in the hands of the bad guys. Since the data is encrypted, it may be considered secure. But if you are using a symmetric algorithm with hardcoded encryption (in Javascript),. The adversaries can simply fetch the common encryption key from JavaScript and decrypt the encrypted data.
As a pentester, whenever you see a hardcoded encryption key in the javascript, this is exactly what is happening. Even though SSL is implemented and protects the channel from being exposed to MiTM actors, it is rarely exposed due to vulnerabilities in the CA or the supporting SSL TLS algorithms themselves.
We have observed that a lot of organizations implement this kind of encryption, leaving the state of their security in a pseudo-secure posture.
The ideal way of implementing encryption in transit is by combining both RSA and AES. The below diagram gives an idea of how to use these algorithms together to get the best form of encryption and data protection against the users’ data.
Initially, the application encrypts data using AES, then encrypts the AES key with RSA’s public key. The encrypted AES key is sent to the server, which decrypts it using RSA’s private key. With the decrypted AES key, the server decrypts the data.
We utilize AES for data encryption due to its superior speed compared to RSA encryption, while employing RSA encryption for transmitting the key to thwart potential Man-in-the-Middle (MiTM) attacks. Each user generates a new AES key, ensuring frequent rotation and greatly minimizing the likelihood of encrypted data exposure through key compromise.
In this case, even if the attacker can intercept the channel and get hold of encrypted data, there is no way for them to decrypt because only the the server holds the private key. As a result,, this is considered to be a more secure way to encrypt the data.
Use of the MD5 (Message Digest Algorithm 5) and SHA-1 (Secure Hash Algorithm 1) hashing algorithms. These were once popular choices for verifying the integrity of data and hashing passwords. However, both MD5 and SHA-1 have been found to have vulnerabilities, making them susceptible to collision attacks where two different inputs produce the same hash. Encryption algorithms like DES are also known for vulnerabilities that can be exploited to decrypt the data. Since the very first examples written are implemented using these algorithms, developers tend to copy-paste these snippets, which use weak algorithms. Attackers can exploit vulnerabilities in these algorithms to gain unauthorized access, steal sensitive information, or tamper with data.
An example of implementing MD5
String filename = "example.txt";
MessageDigest md = MessageDigest.getInstance("MD5");
FileInputStream fis = new FileInputStream(filename);
Example of implementing SHA-256
String filename = "example.txt";
MessageDigest md = MessageDigest.getInstance("SHA-256");
FileInputStream fis = new FileInputStream(filename);
From a functionality point, it may not differ a lot for the developer implementing this, but can raise security concern.
Using insecure random number generation for tasks such as generating one-time passwords (OTP), passcodes, or secret keys can compromise the security of systems and communications. Insecure random number generation can lead to predictable or easily guessable values, making it easier for attackers to intercept or brute force authentication mechanisms and gain unauthorized access to sensitive information or systems.
One common pitfall is the use of simple random number generators (RNGs) that do not provide sufficient randomness or entropy. For example, using the java.util.Random class in Java, which is based on a linear congruential formula, for cryptographic purposes, is not recommended as it can produce predictable sequences of numbers.
public static String generateInsecureOTP() {
Random insecureRandom = new Random(); // uses Random() to generate OTP
StringBuilder otp = new StringBuilder();
for (int i = 0; i < 6; i++) {
otp.append(insecureRandom.nextInt(10));
}
return otp.toString();
}
public static void sendSMS(String recipient, String otp) {
Twilio.init(ACCOUNT_SID, AUTH_TOKEN);
Message message = Message.creator(
new PhoneNumber(recipient),
new PhoneNumber(TWILIO_PHONE_NUMBER),
"Your OTP is: " + generateInsecureOTP())
.create();
System.out.println("OTP sent successfully to " + recipient);
}
A secure way of doing this is using SecureRandom
public static String generateOTP() {
SecureRandom random = new SecureRandom();
StringBuilder otp = new StringBuilder();
for (int i = 0; i < 6; i++) {
otp.append(random.nextInt(10));
}
return otp.toString();
}
Checksums are implemented in payment callback requests to ensure the integrity and authenticity of the data being transmitted. When a payment is processed and a callback request is sent to the merchant’s server to notify them about the payment status, it’s crucial to verify that the data received is not tampered with or corrupted during transmission. While making sure you are using the latest algorithms, it is also required to check if checksum generation is salted.
Salting is a technique used in cryptography, particularly in password hashing, to enhance security by adding random data to the input before hashing. This random data is called a “salt.”
Consider the below HTTP request which is an example of simple callback received about a payment transaction status. The application relies on the checksum to verify the integrity of the data received.
POST /payment_callback HTTP/1.1
Host: example.com
Content-Type: application/json
{
"transaction_id": "123456789",
"amount": 100.50,
"status": "success",
"checksum": "a8dd534163dfd7e1366205499d38f07e"
}
The above request implements checksum by creating an MD5 hash of all the values by joining them with “|”. This looks secure because we are verifying the data that is received and ensuring it is not tampered with by verifying the MD5 hash.
However, an adversary attempts to identify the hash by trying various patterns and characters to join the values. Once the attacker can figure out the hash generation pattern. They can manipulate the data, generate a new checksum for tampered data, and submit the request, bypassing the verification process.
So the solution for this is to use an unpredictable SALT in the hashing process.
byte[] salt = fetchSALTfromDB();
String combinedString = transactionId + "|" + amount + "|" + status + "|" + new String(salt);
By implementing this, the attacker will not have any opportunity to generate checksum on his own without knowing the SALT.
In summary, securing banking applications goes beyond mere compliance; it requires adherence to secure encryption practices. Common pitfalls such as hard-coded keys, weak algorithms, insecure random number generation, and the absence of salting in checksums underscore the need for diligence. Developers must not only meet regulatory standards but also implement encryption in the right and secure manner. Proactive measures, such as robust encryption protocols, proper key management, and regular penetration testing, are essential for safeguarding sensitive financial data and maintaining client trust in the digital age.
Top 7 Most Trusted Cybersecurity Firms in India
Penetration Testing Across Industries: Requirements and Assessment Scope
Security and Penetration Testing for Banking & Finance Companies