The main idea of this project is to provide usable but safe cryptographic operations.
The libsodium project has a similar goal, but does not leverage the features available in modern programming languages such as Swift. The libsodium library is based on NaCl whoose authors discussed the security issues related to cryptographic APIs that are too complicated and error-prone¹ – or as Matthew Green² put it:
OpenSSL is the space shuttle of crypto libraries. It will get you to space, provided you have a team of people to push the ten thousand buttons required to do so. NaCl is more like an elevator — you just press a button and it takes you there. No frills or options.
I like elevators.
To stay with the analogy: libsodium and NaCl prevent any accidents to happen if you press a button for some floor which isn't there. This project tries to prevent the button being there in the first place.
This is achieved by leveraging programming language features in a way that an operation cannot be called with invalid or insecure parameters. Every such call should be prevented at compile time already.
Note that the goal is not to prevent malicious attackers to circumvent the established protection mechanisms by the programming language features but to prevent accidental misuse of cryptographic APIs. If you want to learn more about cryptographic misuse, see our literature collection on cryptographic misuse.
- Repository: https://github.com/blochberger/Tafelsalz
- Documentation: https://blochberger.github.io/Tafelsalz
- Issues: https://github.com/blochberger/Tafelsalz/issues
Check out the project with:
git clone --recursive https://github.com/blochberger/Tafelsalz.git
There are several basic ideas:
- Let the compiler catch/enforce as much as possible.
- Avoid common mistakes.
- Combine convenience and security.
There are basically two different kinds of identities or actors: personas and contacts. For personas you are in posession of the secret keys. For contacts you are only in possession of the public keys.
The secrets for personas are automatically persisted in the system's Keychain. This is the place, where you want to store your secrets, as storing them in the file system might lead to them being compromised easily.
let secretBox = SecretBox()
let plaintext = "Hello, World!".utf8Bytes
let ciphertext = secretBox.encrypt(plaintext: plaintext)
let decrypted = secretBox.decrypt(ciphertext: ciphertext)!
// Create a persona
let alice = Persona(uniqueName: "Alice")
// Once a secret of that persona is used, it will be persisted in the
// system's Keychain.
let secretBox = SecretBox(persona: alice)!
// Use your SecretBox as usual
let plaintext = "Hello, World!".utf8Bytes
let ciphertext = secretBox.encrypt(plaintext: plaintext)
let decrypted = secretBox.decrypt(ciphertext: ciphertext)!
// Forget the persona and remove all related Keychain entries
try! Persona.forget(alice)
let secretBox = SecretBox()
let plaintext = "Hello, World!".utf8Bytes
let padding: Padding = .padded(blockSize: 16)
let ciphertext = secretBox.encrypt(plaintext: plaintext, padding: padding)
let decrypted = secretBox.decrypt(ciphertext: ciphertext, padding: padding)!
let password = Password("Correct Horse Battery Staple")!
let hashedPassword = password.hash()!
// Store `hashedPassword.string` to database.
// If a user wants to authenticate, just read it from the database and
// verify it against the password given by the user.
if hashedPassword.isVerified(by: password) {
// The user is authenticated successfully.
}
let data = "Hello, World!".utf8Bytes
let hash = GenericHash(bytes: data)
// Create a persona
let alice = Persona(uniqueName: "Alice")
// Generate a personalized hash for that persona
let data = "Hello, World!".utf8Bytes
let hash = GenericHash(bytes: data, for: alice)
// Forget the persona and remove all related Keychain entries
try! Persona.forget(alice)
let context = MasterKey.Context("Examples")!
let masterKey = MasterKey()
let subKey1 = masterKey.derive(sizeInBytes: MasterKey.DerivedKey.MinimumSizeInBytes, with: 0, and: context)!
let subKey2 = masterKey.derive(sizeInBytes: MasterKey.DerivedKey.MinimumSizeInBytes, with: 1, and: context)!
// You can also derive a key in order to use it with secret boxes
let secretBox = SecretBox(secretKey: masterKey.derive(with: 0, and: context))
let plaintext = "Hello, World!".utf8Bytes
let password = Password("Correct Horse Battery Staple")!
// Derive a new key from a password
let derivedKey1 = password.derive(sizeInBytes: SecretBox.SecretKey.SizeInBytes)!
let secretBox1 = SecretBox(secretKey: SecretBox.SecretKey(derivedKey1))
let ciphertext = derivedKey1.publicParameters + secretBox1.encrypt(plaintext: plaintext).bytes
// Derive a previously generated key from a password
let (salt, complexity, memory) = Password.DerivedKey.extractPublicParameters(bytes: ciphertext)!
let derivedKey2 = password.derive(sizeInBytes: SecretBox.SecretKey.SizeInBytes, complexity: complexity, memory: memory, salt: salt)!
let secretBox2 = SecretBox(secretKey: SecretBox.SecretKey(derivedKey2))
let authenticatedCiphertextBytes = Bytes(ciphertext[Int(Password.DerivedKey.SizeOfPublicParametersInBytes)...])
let authenticatedCiphertext = SecretBox.AuthenticatedCiphertext(bytes: authenticatedCiphertextBytes)!
let decrypted = secretBox2.decrypt(ciphertext: authenticatedCiphertext)!
let alice = KeyExchange(side: .client)
let bob = KeyExchange(side: .server)
let alicesSessionKey = alice.sessionKey(for: bob.publicKey)
let bobsSessionKey = bob.sessionKey(for: alice.publicKey)
// alicesSessionKey == bobsSessionKey
- D. J. Bernstein, T. Lange, and P. Schwabe, The Security Impact of a New Cryptographic Library in Progress in Cryptology – LATINCRYPT 2012 – 2nd International Conference on Cryptology and Information Security in Latin America, Santiago, Chile, October 7-10, 2012. Proceedings (A. Hevia and G. Neven, eds.), pp. 159–176
- M. Green, The Anatomy of a Bad Idea, 2012