Hardcoded Cryptographic Key

ID

swift.hardcoded_cryptographic_key

Severity

critical

Resource

Predictability

Language

Swift

Tags

CWE:321, MASWE:0013, NIST.SP.800-53, OWASP:2021:A2, PCI-DSS:3.6.3, crypto

Description

Cryptographic keys that are hardcoded into source code can be easily extracted and exploited by malicious actors. This practice compromises the security of the application, as these keys are not changeable without altering the source code.

Rationale

Hardcoding cryptographic keys in source code is a risky practice as it exposes sensitive information that should remain secret. The concern arises because hardcoded keys are not modifiable without a code change, making them an attractive target for attackers who can access the source code or binaries.

Consider the following Swift example using CommonCrypto:

import Foundation
import CommonCrypto

class Encryptor {
    // FLAW: Hardcoded cryptographic key
    private static let key: [UInt8] = [
        0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
        0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
    ]

    // FLAW: Hardcoded IV (should also be random)
    private static let iv: [UInt8] = [
        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
        0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10
    ]

    static func encrypt(_ data: Data) -> Data? {
        var cryptData = Data(count: data.count + kCCBlockSizeAES128)
        var numBytesEncrypted: size_t = 0

        let cryptStatus = data.withUnsafeBytes { dataBytes in
            cryptData.withUnsafeMutableBytes { cryptBytes in
                key.withUnsafeBytes { keyBytes in
                    iv.withUnsafeBytes { ivBytes in
                        // FLAW: Using hardcoded key
                        CCCrypt(
                            CCOperation(kCCEncrypt),
                            CCAlgorithm(kCCAlgorithmAES),
                            CCOptions(kCCOptionPKCS7Padding),
                            keyBytes.baseAddress, key.count,  // HARDCODED KEY
                            ivBytes.baseAddress,
                            dataBytes.baseAddress, data.count,
                            cryptBytes.baseAddress, cryptData.count,
                            &numBytesEncrypted
                        )
                    }
                }
            }
        }

        guard cryptStatus == kCCSuccess else { return nil }
        cryptData.count = numBytesEncrypted
        return cryptData
    }
}

Another common vulnerable pattern using CryptoKit:

import Foundation
import CryptoKit

class SecureStorage {
    // FLAW: Hardcoded encryption key
    private static let encryptionKey = "MySecretKey12345".data(using: .utf8)!

    func encryptData(_ data: Data) throws -> (Data, AES.GCM.Nonce) {
        // FLAW: Creating SymmetricKey from hardcoded data
        let key = SymmetricKey(data: SecureStorage.encryptionKey)
        let sealedBox = try AES.GCM.seal(data, using: key)
        return (sealedBox.ciphertext, sealedBox.nonce)
    }
}

In these examples, the cryptographic keys are hardcoded directly into the class. If this code is compiled and distributed, anyone with access to the binary can retrieve and misuse the keys, nullifying any intended cryptographic protection.

Remediation

To remediate this vulnerability, cryptographic keys should be managed securely, never hardcoding them in source code. Instead, use environmental variables, configuration files, or dedicated secrets management services that provide secure storage and retrieval of sensitive data.

An alternative is to perform cryptographic operations using an external, managed service. Known as Key Management Services (KMS), they provide different features including key generation and storage, key rotation and lifecycle management, encryption / decryption and other cryptographic operations like digital signatures, key wrapping, secure random number generation, etc.

For the previous hardcoded key, the fix retrieves the key material from a secure location such as environment variables or the Keychain. A Key Management Service (KMS) or secrets vault could be used alternatively.

import Foundation
import CryptoKit
import Security

class SecureEncryptor {

    func encrypt(_ data: Data) throws -> (ciphertext: Data, nonce: AES.GCM.Nonce, tag: Data) {
        // FIXED: Retrieve key from secure location
        let key = try retrieveKeyFromKeychain()

        // Encrypt using AES-GCM
        let sealedBox = try AES.GCM.seal(data, using: key)

        return (
            ciphertext: sealedBox.ciphertext,
            nonce: sealedBox.nonce,
            tag: sealedBox.tag
        )
    }

    private func retrieveKeyFromKeychain() throws -> SymmetricKey {
        // Query the Keychain for the encryption key
        let query: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrApplicationTag as String: "com.example.app.encryptionkey",
            kSecAttrKeyType as String: kSecAttrKeyTypeAES,
            kSecReturnData as String: true
        ]

        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)

        guard status == errSecSuccess,
              let keyData = item as? Data else {
            throw EncryptionError.keyNotFound
        }

        return SymmetricKey(data: keyData)
    }

    // Alternative: Retrieve from environment variable (less secure than Keychain)
    private func retrieveKeyFromEnvironment() throws -> SymmetricKey {
        guard let base64Key = ProcessInfo.processInfo.environment["ENCRYPTION_KEY"],
              let keyData = Data(base64Encoded: base64Key) else {
            throw EncryptionError.keyNotFound
        }

        return SymmetricKey(data: keyData)
    }
}

enum EncryptionError: Error {
    case keyNotFound
}

Best practice for key generation and storage:

import Foundation
import CryptoKit
import Security

class KeyManager {

    // Generate a new random key and store it securely in Keychain
    static func generateAndStoreKey() throws {
        // Generate a secure random 256-bit key
        let key = SymmetricKey(size: .bits256)

        // Extract key data
        let keyData = key.withUnsafeBytes { Data($0) }

        // Store in Keychain
        let query: [String: Any] = [
            kSecClass as String: kSecClassKey,
            kSecAttrApplicationTag as String: "com.example.app.encryptionkey",
            kSecAttrKeyType as String: kSecAttrKeyTypeAES,
            kSecValueData as String: keyData,
            kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
        ]

        let status = SecItemAdd(query as CFDictionary, nil)

        guard status == errSecSuccess else {
            throw KeyError.storageFailure
        }
    }
}

enum KeyError: Error {
    case storageFailure
}

In the improved examples, keys are retrieved from secure storage mechanisms like the Keychain or environment variables. This approach enhances security by keeping cryptographic keys outside the source code, allowing them to be rotated and managed without altering the application code.

References