Cryptographic Failures is the second most important category of vulnerabilities listed in the OWASP Top 10 for 2021. In this blog post, we’ll cover techniques to encrypt and hash data in Apex when that data needs to be transmitted to or from an external system. We’ll share code examples and we’ll explain when to choose each technique.
OWASP #2: Cryptographic Failures
OWASP (Open Web Application Security Project) is a nonprofit foundation that works to improve software security. Recently, OWASP published its Top 10 list for 2021. One of the categories that has moved up on the list is Cryptographic Failures, which is now in the second position (A02). This means that today this problem is even more critical and frequent than it was in 2017 (when the category was named “Sensitive Data Exposure“).
This category covers failures related to cryptography, including the lack of cryptographic mechanisms, which often leads to exposure of sensitive data. This is especially important for integrations, where data is transmitted from one system to another. Luckily, the Salesforce Platform and Apex have cryptographic utilities and algorithms in place to ensure that data is securely transmitted without compromise.
What data needs to be encrypted?
The first question to answer here is: what kind of data should be encrypted, and how? Passwords, credit card numbers, health records, personal information, and business secrets are some examples of sensitive data. We should encrypt such data, not only for our own security, but also (in some cases) to comply with regulations like the GDPR or the PCI data security standard. It is recommended to encrypt data at rest (on disk) and when it’s being transmitted through the network (in transit) — even if we are using secure protocols like HTTPS. In this blog post, we’ll focus on encrypting and hashing data in transit. If you want to know more about encryption at rest, take a look at our Shield Platform Encryption feature.
Information security key concepts
Different algorithms are available to encrypt data in transit, and each can enforce one or more of the following characteristics of the communication process:
- Confidentiality (secrecy): ensures that a message is transmitted in a format (normally encrypted), so that unauthorized users are not able disclose what the message contains.
- Integrity: ensures that the message has not been altered during the transmission (not tampered with). Confidentiality and integrity are independent. For instance, a a message can be transmitted in clear text, not being secret, while still preserving its integrity.
- Authenticity: ensures that the message was sent by the sender who claims it. Algorithms that ensure authenticity often imply integrity as well.
- Non-repudiation: is a stronger concept than authenticity. It adds legal proof that ensures that the sender sent the message.
The Apex Crypto class contains pre-built functions to help you implement secure encryption algorithms. Let’s take a look at some of them.
Crypto.encrypt() method allows you to encrypt data before it is sent to a receiver using the AES algorithm. This ensures confidentiality. The AES algorithm is a block cipher (operates with blocks of a fixed size) algorithm that takes plain text in blocks of 128 bits and converts them to cipher text. The mode of operation for AES in Apex is CBC mode. The cipher text follows the PKCS7 padding syntax. Equivalently, the
Crypto.decrypt() method allows you to decrypt data that’s been received in cypher text.
You can choose between AES128, AES192, and AES256 for the algorithm. Crypto classes often offer multiple algorithm versions, and when possible, you should choose the highest bit option common to Salesforce and the third-party system. Another factor to consider is compute time: the more complex the algorithm, the more time it will take to encrypt and decrypt.
AES uses a symmetric key of 128, 192, or 256 bits (the later being the most secure option). The chosen key length does not need to match the chosen algorithm version. There’s a
Crypto.generateAESKey() method that you can use to generate the key.
A symmetric key is a shared secret that both the sender and receiver have to encrypt and decrypt the message. Contrast this with asymmetric keys, where the sender encrypts using the receiver’s public key, and the receiver decrypts using his/her own private key. There’s one particular situation — digital signatures — where the sender uses their private key to encrypt (see the section on this below).
Symmetric keys need to be shared in a secure way. The recommendation is to share them offline or send them encrypted, typically using PKI. Also, remember that keys should be stored in a safe way in Salesforce. In managed packages, use a protected custom metadata record or a protected custom setting for that purpose.
The AES algorithm also needs an initialization vector (IV). The size of the IV is based on the algorithm mode. For CBC, it’s 128 bits. This is a random number used to ensure that the same value encrypted multiple times doesn’t always result in the same encrypted value.
The following diagram shows where the IV is used in CBC mode. Note how the output would be the same for a given input and key if there was no IV.
To decrypt, you not only need the symmetric key, but also the IV. You can generate a custom IV for encrypting the data, and send it together with the ciphertext for decryption on the receiver. Bear in mind that the IV needs to be different on each operation.
In most cases, it’s easier to use the
Crypto.encryptWithManagedIV() method that generates a random initialization vector for you, and transmits it in the first 128 bits (16 bytes) of the encrypted Blob. In that case, you decrypt the data with
Crypto.decryptWithManagedIV(). We strongly recommend that you follow this approach.
Here you have some sample Apex code that shows how to use these methods:
Note that in all the examples, we’ve encoded Blobs (variables that hold binary data) in Base64 (binary to text encoding), so that you can see a String when testing them, but data can be transmitted in Blob format.
Crypto.encryptWithManagedIV(), the external system will have to obtain the IV from the first 128 bits (16 bytes) of the received ciphertext for decryption.
Crypto.generateDigest() method generates a one-way hash digest using MD5, SHA1, SHA256, or SHA512 (the later being the most secure one). The hash digest process applies an algorithm to the input, resulting in a final string of a fixed length. Hash functions are one-way by design. It is infeasible to reverse a cryptographic hash. While you cannot reverse a hash, the algorithms are designed to be compatible, allowing you to hash the same information on multiple systems. Each system, using the same algorithm, generates an identical hash. Then, the receiver will be sure that the message has not been tampered with — the integrity of the message has been preserved. Notice that generating and checking a hash digest doesn’t ensure confidentiality. If you need the message to be confidential, you’ll have to additionally use an encryption algorithm. Also, a hash by itself doesn’t ensure authenticity. We’ll cover HMAC and digital signatures, which do provide it, later in the post.
Here you have some Apex code that shows how to use these methods for an emitter and for a receiver of the message:
areEqualConstantTime() is a method that compares two Blobs in constant time in order to avoid timing attack effects.
Crypto.generateMac() method (see reference) generates a Hash-Based Message Authentication Code (HMAC). With HMAC, a secret key (symmetric) is used to derive two keys that are used in the hashing process. This way, not only is integrity assured, but also authenticity as we can ensure that the MAC for the message was generated using the secret key. The supported HMAC algorithms are HMACMD5, HMACSHA1, HMACSHA256, and HMACSHA512 (the later being the most secure).
The only restriction for the key is that it is less than 4KB. It’s recommended to use different keys for the encryption of the message and for the HMAC.
Here you have some Apex code to generate a HMAC in the sender and check for its validity on the receiver.
Although symmetric keys should be shared in a safe way, as they are shared, there’s always a possibility that somebody else may have used them in order to pretend to be the sender. That’s why we use digital signatures to ensure non-repudiation. Digital signatures use an asymmetric key in which the private part of the key is never shared. So, when a message is digitally signed, it’s considered legally proven that the sender sent the message.
Crypto.sign() method, you can compute a unique signature for the message using the specified signing algorithm and the supplied private key — in this case, the sender’s portion of an asymmetrical key. The valid algorithms are RSA, RSA-SHA1, RSA-SHA256, RSA-SHA384, RSA-SHA512, ECDSA-SHA256, ECDSA-SHA384, and ECDSA-SHA512. There are several factors to take into account when comparing RSA and ECDSHA algorithms, so evaluate them in depth before choosing. In this case, integrity, authenticity, and non-repudiation of the message are enforced.
Here you have some code examples to better understand how to use the methods:
Alternatively, you can sign a message using
Crypto.signWithCertificate(), which takes the name of a X509 certificate that contains a private key, and then calls
Crypto.sign() internally. Use
Crypto.signXML() to sign an XML document.
Signing a document digitally is the most robust approach, but bear in mind that the greater the complexity, the longer will take the algorithm to execute. The execution time will be an important factor to take into account in the algorithm selection.
Combining encryption and signature algorithms
Combining encryption and signature algorithms will provide any combination of confidentiality, integrity, and non-repudiation features that you need. For instance, take a look at this example in which we combine encryption with a digital signature algorithm:
In this blog post, we’ve covered different encryption and signature techniques that you can use to avoid cryptographic failures when transmitting data. Each of the techniques ensures certain aspects of the communication process that you can find summarized in this table:
Also, consider the algorithm complexity versus the time it takes to execute when choosing the right algorithm for your use case.
We’ve added all the code examples highlighted in this blog post to Apex Recipes. Also, learn more about encryption in the Trailhead module: Use Encryption in Custom Applications.
About the author
Alba Rivas works as a Principal Developer Advocate at Salesforce. She focuses on Lightning Web Components and Lightning adoption strategy. You can follow her on Twitter @AlbaSFDC.