Hardcoded Cryptographic Salt
ID |
swift.hardcoded_cryptographic_salt |
Severity |
high |
Resource |
Predictability |
Language |
Swift |
Tags |
CWE:760, MASWE:0013, NIST.SP.800-53, OWASP:2021:A2, PCI-DSS:6.5.3 |
Description
"Salting" is a technique used in some cryptographic operations:
-
With password hashing, a salt is typically used to prevent dictionary or 'rainbow' table attacks using massive pre-computed tables of hashed common passwords.
-
With key derivation functions (KDF), such as PBKDF2, scrypt or Argon2, a salt is used to prevent the same key being derived from the same input password.
-
With digital signatures and message authentication schemes, salts often add randomness to the message being signed or authenticated, making it harder for attackers to forge signatures or extract private keys through cryptoanalysis.
The use of a hardcoded cryptographic salt in applications can severely compromise the security of cryptographic processes. In password hashing, for example, a salt is typically used to ensure that even if two users have the same password, their hashed outputs will differ.
However, if the salt is hardcoded into the application’s source code, it becomes predictable and defeats the purpose of introducing randomness, making the application susceptible to attacks such as dictionary and rainbow table attacks.
Rationale
Hardcoding cryptographic salts within the source code is a common error that can reduce the effectiveness of hash functions used to secure sensitive information, like passwords.
A salt should be unique and random for each cryptographic operation to ensure that the results are distinct, even for identical inputs. By hardcoding a salt, attackers can reverse-engineer the application to discover the salt, allowing them to perform attacks that target the hashed data.
Consider the following vulnerable Swift example using CryptoSwift:
import CryptoSwift
class PasswordHasher {
// FLAW: Hardcoded salt
private static let constantSalt: [UInt8] = [0x2a, 0x3a, 0x80, 0x05, 0xaf, 0x80, 0x05, 0xaf]
func hashPassword(_ password: String) throws -> String {
// FLAW: Using constant salt for PBKDF2
let pbkdf2 = try PBKDF2(
password: Array(password.utf8),
salt: PasswordHasher.constantSalt, // FLAW
iterations: 10000,
keyLength: 32,
variant: .sha256
)
return try pbkdf2.calculate().toHexString()
}
}
Another vulnerable example with CommonCrypto:
import Foundation
import CommonCrypto
func deriveKeyFromPassword_Vulnerable(_ password: String) -> Data? {
// FLAW: Hardcoded salt
let salt = "ConstantSaltValue123".data(using: .utf8)! // FLAW
var derivedKey = Data(count: 32)
let status = derivedKey.withUnsafeMutableBytes { derivedKeyBytes in
password.data(using: .utf8)!.withUnsafeBytes { passwordBytes in
salt.withUnsafeBytes { saltBytes in
CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
passwordBytes.baseAddress?.assumingMemoryBound(to: Int8.self),
password.count,
// predicatable salt
saltBytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
salt.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
10000,
derivedKeyBytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
32
)
}
}
}
guard status == kCCSuccess else { return nil }
return derivedKey
}
In these examples, the same salt is used for all password hashing operations. An attacker who obtains these hashes can build a single rainbow table and crack multiple passwords efficiently.
Remediation
To remediate the issue of a hardcoded cryptographic salt, developers should generate a unique, random salt for each cryptographic operation. This ensures the robustness of the hash function by making the output unique even for identical inputs.
Generate a unique, non-predictable salt for each cryptographic operation. Store the generated salt in clear text along with the hashed/encrypted data to allow so the salt could be retrieved during the verification process. And never reuse a salt !
| In the 1970s 12 bits of salt were common and appropriate for the computational and storage capacity at that time. Today, at least 128 bits of salt are recommended and generally are robust enough for most use cases. |
Secure implementation using CryptoSwift with random salt:
import CryptoSwift
import Foundation
class SecurePasswordHasher {
struct HashedPassword {
let hash: Data
let salt: Data
}
func hashPassword(_ password: String) throws -> HashedPassword {
// FIXED: Generate random salt for each password
let salt = generateRandomSalt(length: 16)
let pbkdf2 = try PBKDF2(
password: Array(password.utf8),
salt: salt,
iterations: 100000, // Increased iterations for better security
keyLength: 32,
variant: .sha256
)
let hash = try pbkdf2.calculate()
return HashedPassword(hash: Data(hash), salt: Data(salt))
}
private func generateRandomSalt(length: Int) -> [UInt8] {
return (0..<length).map { _ in UInt8.random(in: 0...UInt8.max) }
}
func verifyPassword(_ password: String, against stored: HashedPassword) throws -> Bool {
// Use the stored salt for verification
let pbkdf2 = try PBKDF2(
password: Array(password.utf8),
salt: Array(stored.salt),
iterations: 100000,
keyLength: 32,
variant: .sha256
)
let computedHash = try pbkdf2.calculate()
return Data(computedHash) == stored.hash
}
}
Secure implementation using CommonCrypto with random salt:
import Foundation
import CommonCrypto
struct PasswordHash {
let hash: Data
let salt: Data
let iterations: Int
}
func secureHashPassword(_ password: String, iterations: Int = 100000) throws -> PasswordHash {
// FIXED: Generate cryptographically secure random salt
var salt = Data(count: 16)
let result = salt.withUnsafeMutableBytes { saltBytes in
SecRandomCopyBytes(kSecRandomDefault, 16, saltBytes.baseAddress!)
}
guard result == errSecSuccess else {
throw NSError(domain: "RandomGenerationError", code: Int(result), userInfo: nil)
}
// Derive key using random salt
var derivedKey = Data(count: 32)
let status = derivedKey.withUnsafeMutableBytes { derivedKeyBytes in
password.data(using: .utf8)!.withUnsafeBytes { passwordBytes in
salt.withUnsafeBytes { saltBytes in
CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
passwordBytes.baseAddress?.assumingMemoryBound(to: Int8.self),
password.count,
saltBytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
salt.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
UInt32(iterations),
derivedKeyBytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
32
)
}
}
}
guard status == kCCSuccess else {
throw NSError(domain: "PBKDFError", code: Int(status), userInfo: nil)
}
return PasswordHash(hash: derivedKey, salt: salt, iterations: iterations)
}
References
-
MASWE-0013: Hardcoded Cryptographic Keys in Use.