Open chachapoly sealed box after removing and recreating a symmetric key (iOS)? - swift

I am in a bit of a roadblock on my iOS app (first app for me), I want to encrypt data that I send to a server.
In order to do so I generate a symmetric key that I store in the keychain.
The key is generated in the following way:
SymmetricKey(data: password)
In this function the password is actually a computed SHA256 made like this:
SHA256.hash(data: password) which gives me a digest and from that I extract the data representation to create my key.
Now when I encrypt data I do the following
var encryptedData: Data = Data()
if let key: SymmetricKey = try? readKey(account: encryptingKeyAccount){
encryptedData = try! ChaChaPoly.seal(rawData, using: key).combined
}
return encryptedData
This returns me the data that I then send to the server.
In order to decrypt the data I do the following:
var decryptedData: Data = Data()
if let key: SymmetricKey = try? readKey(account: encryptingKeyAccount) {
let sealedBox = try! ChaChaPoly.SealedBox(combined: encryptedData)
decryptedData = try! ChaChaPoly.open(sealedBox, using: key)
}
return decryptedData
Now my problem is, if I logout (which means deleting the keys from the phone) and log back in (which in my mind recreates the same keys that were created before) then I am unable to decrypt my data... I have the following error when I try to open the box:
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: CryptoKit.CryptoKitError.authenticationFailure
Which I believe is due to how I create the key, which is not the same as the one that was deleted previously...
How can I fix this ?
Thanks !

Related

How to decrypt an encrypted data came from server with AES decryption?

I'm working on a project that'll open PDF books inside application. I'm fetching the books from the server and they're coming as locked pdf pages and I'm also fetching each pages' password with another API.
The problem is, the list of passwords are coming as AES encrypted. I can not decrypt them properly. I don't know what am I missing. My code is below. Waiting for your answers. Thank you :) (I'm using CrytoSwift library)
(I'm taking key and iv with an another function and keeping them in two different variables.)
import CryptoSwift
var myEncryptedData : String = ""
var key32 : String = ""
var iv16: String = ""
func decryptData() {
do {
let aes = try AES(key: Array(key32.utf8), blockMode: CBC(iv: Array(iv16.utf8)), padding: .pkcs7)
let ciphertext = try aes.decrypt(myEncryptedData.bytes)
print(String(data: Data(ciphertext), encoding: .utf8));
} catch {
print(error)
}
}
I tried to decrypt encryptedString with AES decryption by using CryptoSwift library but I couldn't fetch the data that I expected.
I just added this code just below "do" and it worked
let d = Data(base64Encoded: encryptedString)
Working version is:
do {
let d = Data(base64Encoded: encryptedString)
let aes = try AES(key: Array(key32.utf8), blockMode: CBC(iv: Array(iv16.utf8)), padding: .pkcs7)
let ciphertext = try aes.decrypt(d!.bytes)
let stringCipherText = String(data: Data(ciphertext), encoding: .utf8)
} catch {
print(error)
}

Xcode 11 Swift 5 CryptoKit share SymmetricKey

I'm starting to learn de-/encrypting with the CryptoKit.
Everything works fine, but I can not share my generated SymmetricKey.
Example:
let key = SymmetricKey(size: .bits256)
Well, I generate a symmetric key. Now I want to share the key, but how I can do that?
Inside the debugger the variable key is empty?
I check the encryption and decryption - works well - output shows the encrypted and decrypted data. How can I save my variable key for distribution?
I found a solution:
let savedKey = key.withUnsafeBytes {Data(Array($0)).base64EncodedString()}
This works great, but how can I save the variable savedKey (String) back into the variable key (SymmetricKey)?
You can do that by converting key string to Data and retrieve the key from it
let key = SymmetricKey(size: .bits256)
let savedKey = key.withUnsafeBytes {Data(Array($0)).base64EncodedString()}
if let keyData = Data(base64Encoded: savedKey) {
let retrievedKey = SymmetricKey(data: keyData)
}
Hope this helps :)

How do I handle salting a password hash in Vapor 3?

I'm building a basic authentication setup similar to how it's used in Vapor's auth-template template (from here). I have everything set up the same way as in the template.
However, I would like to add salt. I can generate a salt for a user upon creation:
static func create(_ req: Request, newUserRequest user: CreateUserRequest) throws -> Future<User.Public> {
return User.query(on: req).filter(\.username == user.username).first().flatMap { existingUser in
guard existingUser == nil else {
throw Abort(.badRequest, reason: "A user with the given username already exists.")
}
guard user.password == user.passwordVerification else {
throw Abort(.badRequest, reason: "Given passwords did not match.")
}
let count = 16
var pw_salt_data = Data(count: count)
let _ = pw_salt_data.withUnsafeMutableBytes { mutableBytes in
SecRandomCopyBytes(kSecRandomDefault, count, mutableBytes)
}
let pw_salt = try BCrypt.hash(pw_salt_data.base64EncodedString())
let pw_hash = try BCrypt.hash(pw_salt + user.password)
return User(id: nil, username: user.username, pw_hash: pw_hash, pw_salt: pw_salt, email: user.email).save(on: req).toPublic()
}
}
But there's no way to retrieve that salt when performing authentication during login:
static func login(_ req: Request) throws -> Future<UserToken> {
let user = try req.requireAuthenticated(User.self)
let token = try UserToken.create(userID: user.requireID())
return token.save(on: req)
}
I want the salt to be randomly generated for each user and stored in the database as a separate column from the hashed password to be used later during authentication.
Is there a standardized way to handle salting a password hash in Vapor 3?
The way it works in Vapor is that each BCrypt hash has a unique salt that’s saved with the password in the database. The BCrypt default functions in Vapor expect this.
If you want to go down a different route have a look at the function to hash a password - that takes a salt. You can then save that in its own field and retrieve and when you verify the password. Honestly I’d say to just use the defaults unless you have a very specific reason not to
You hash the password using BCrypt. BCrypt is already part of the Vapor dependencies.
BCrypt.hash("vapor", cost: 4)
This will hash the string "vapor", using a randomly generated salt, with complexity 4. Choosing the cost is subjective and arbitrary but it is recommended that real-world secure applications should have a cost factor above 10-12. If you don't like the salt being randomly generated by BCrypt, and you want to generate your own salt, you can provide the salt to the hash function, which has the following signature:
public func hash(_ plaintext: LosslessDataConvertible, cost: Int = 12, salt: LosslessDataConvertible? = nil) throws -> String
Documentation says that the salt must be 16-bytes if manually provided.
This is a sample hash:
$2a$04$/nqhWqplnughhq6mlKmi8.raprxoG/dczY8kdbOKm.zC5sPu.2IBi
As you can see it includes auxiliary information such as the complexity, the type of the algorithm and the salt, everything necessary to do the verification. If you provided your own salt it will also be part of the final hash and you don't need to separately provide it. You can do verification as written below.
try BCrypt.verify("vapor", created: hashedPasswordSavedInDatabase)

Swift Sodium - Anonymous Encryption (Sealed Boxes)

I am trying to encrypt value using swift sodium with given public key.
However, the encrypted value is not the same as what's produced on server side.
I am not sure whether this piece of coding is right in swift.
The steps are similar to how its done in java.
Assume public key is in base64 string format.
Java:
String pubKey = "w6mjd11n9w9ncKfcuR888Ygi02ou+46ocIajlcUEmQ=";
String secret = "hithere"
byte[] pubKeyBytes = Base64.decode(pubKey,0);
SealedBox sealedDeal = new SealedBox(pubKeyBytes);
byte[] c = sealedDeal.encrypt(secret.getBytes());
String ciphertext = Base64.encodeToString(c, 0);
Swift:
let pubKey = "w6mjd11n9w9ncKfcuR888Ygi02ou+46ocIajlcUEmQ="
let dataDecoded:NSData = NSData(base64Encoded: pubKey, options: NSData.Base64DecodingOptions(rawValue: 0))!
let secret = "hithere".toData()!
let c : Data = sodium.box.seal(message: secret, recipientPublicKey: dataDecoded as Box.PublicKey)!
let ciphertext = c.base64EncodedString(options: .init(rawValue: 0))
Please tell me know what's wrong with the swift equivalent coding.
Thanks alot.
The encrypted value is supposed to be different, so that ciphertexts resulting from equivalent plaintexts are indistinguishable (see Ciphertext indistinguishability).
sodium.box.seal internally generates new nonce every time you are encrypting message, #Max is right, this is normal behave
You can use Detached mode to give same nonce, but this is a very bad idea
In your example you have used Anonymous Encryption I suggest you to take a look at Authenticated Encryption

Import p12 certificate with full access to private key for my application (OS X)

I'm accessing the https webserver with certificate authentication from mac app, so I need to handle authentication and provide my certificate (URLSession -> NSURLAuthenticationMethodClientCertificate -> call SecPKCS12Import and extract identity from imported certificate -> create NSURLCredential from identity and provide it in completionHandler to the server) .
But after each https request the dialog box "MYAPP wants to sign using "privateKey" in your keychain" is displayed:
I want to avoid this message. My app is signed correctly. I think I need to set access for the certificate while importing (full access for my app), I'm trying to do it using SecAccessCreate and SecPKCS12Import options:
func extractIdentity(certData:NSData, certPassword:String) -> IdentityAndTrust {
var identityAndTrust:IdentityAndTrust!
var securityError:OSStatus = errSecSuccess
var items:CFArray?
//let certOptions:CFDictionary = [ kSecImportExportPassphrase.takeRetainedValue() as String: certPassword ];
let index: CFIndex = 2
let passwordKey = kSecImportExportPassphrase as String;
let passwordValue: CFString = "PASSWORD";
let accessKey = kSecImportExportAccess as String;
var access:SecAccessRef? = nil;
SecAccessCreate("CERTIFICATE_NAME", nil, &access);
var keys = [unsafeAddressOf(accessKey), unsafeAddressOf(passwordKey)]
var values = [unsafeAddressOf(access!), unsafeAddressOf(passwordValue)]
var keyCallbacks = kCFTypeDictionaryKeyCallBacks
var valueCallbacks = kCFTypeDictionaryValueCallBacks
let options = CFDictionaryCreate(kCFAllocatorDefault, &keys, &values, index, &keyCallbacks, &valueCallbacks)
// import certificate to read its entries
securityError = SecPKCS12Import(certData, options, &items);
if securityError == errSecSuccess {
let certItems:CFArray = items as CFArray!;
let certItemsArray:Array = certItems as Array
let dict:AnyObject? = certItemsArray.first;
if let certEntry:Dictionary = dict as? Dictionary<String, AnyObject> {
// grab the identity
let identityPointer:AnyObject? = certEntry["identity"];
let secIdentityRef:SecIdentityRef = identityPointer as! SecIdentityRef!;
// grab the trust
let trustPointer:AnyObject? = certEntry["trust"];
let trustRef:SecTrustRef = trustPointer as! SecTrustRef;
// grab the certificate chain
var certRef:SecCertificate?
SecIdentityCopyCertificate(secIdentityRef, &certRef);
let certArray:NSMutableArray = NSMutableArray();
certArray.addObject(certRef as SecCertificateRef!);
identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray: certArray);
}
}
return identityAndTrust;
}
Anyway it doesn't work. How can I avoid this dialog box?
This thread How do I add authorizations to code sign an app from new keychain without any human interaction is related to importing the certificate using "security" command, and suggestion was to use -A or -T flags while importing the certificate, but can I do it programmatically without console commands?
You're probably building and running the application multiple times, which means that the cert was added to the keychain the first time, and the executable that did it was authorized to use the private key. However when you made some changes and rebuilt the project, the executable was replaced and the new executable doesn't have access to the private key (this is also an issue when your users have to update your software or reinstall for any reason).
What I found I had to do was remove the cert from the keychain after I used it, and re-add it before every use. However I've read that you can grant permission to an app identifier so that may work in your case too.
I have a code sample in my question's answer: How do I kill the popup?