I want to validate or verify JWT signature.
I don't succeed to use SecKey, to use SecKeyVerifySignature function.
Here is my code:
let parts = session.token.components(separatedBy: ".")
let header = parts[0]
let payload = parts[1]
let signature = Data(base64Encoded: parts[2], options: .ignoreUnknownCharacters)
let pubKey = "-----BEGIN PUBLIC KEY-----xxxxxxxx/xxxxxxxx/xxxxxxxx/xxxxxxxx/xxxxxxx-----END PUBLIC KEY-----"
let encodedPubKey = Data(pubKey.utf8).base64EncodedString()
let attributes: [String:Any] = [
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
]
let secKey = decodeSecKeyFromBase64(encodedKey: encodedPubKey)
let signingInput = (header + "." + payload).data(using: .ascii)!
let validSignature = SecKeyVerifySignature(secKey!, .rsaSignatureMessagePKCS1v15SHA256, signingInput as CFData, signature as! CFData, nil)
and function decodeSecKeyFromBase64:
// Extract secKey from encoded string - defaults to extracting public keys
func decodeSecKeyFromBase64(encodedKey: String, isPrivate: Bool = false) -> SecKey? {
var keyClass = kSecAttrKeyClassPublic
if isPrivate {
keyClass = kSecAttrKeyClassPrivate
}
let attributes: [String:Any] =
[
kSecAttrKeyClass as String: keyClass,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
]
guard let secKeyData = Data.init(base64Encoded: encodedKey) else {
print("Error: invalid encodedKey, cannot extract data")
return nil
}
guard let secKey = SecKeyCreateWithData(secKeyData as CFData, attributes as CFDictionary, nil) else {
print("Error: Problem in SecKeyCreateWithData()")
return nil
}
return secKey
}
I don't know if I have to encode pubKey or not before to valid signature. It prints Error: Problem in SecKeyCreateWithData() from decodeSecKeyFromBase64
Related
I want to get the values (group, ID, password) stored in the keychain.
I can get only the password. I want to get group and ID.
Get method is getKeyChain
import Foundation
// キーチェーンの保存・取得を管理する
extension ViewController {
// 保存
func saveKeyChain() {
// group
let grp = "AA"
// ID
let id = "0001"
// password
let str = "test"
let data = str.data(using: .utf8)
guard let _data = data else {
return
}
// APIを実行する際の引数設定
// これをSecItemCopyMatching の第一引数に渡すと結果を受け取ることができる
let dic: [String: Any] = [kSecClass as String: kSecClassGenericPassword, // パスワードクラス
kSecAttrGeneric as String: grp, // 自由項目(グループとする)
kSecAttrAccount as String: id, // アカウント(ログインID)
kSecValueData as String: _data, // パスワード本体
kSecAttrService as String: "test"] // サービス名
print(dic)
var itemAddStatus: OSStatus?
// 保存データが存在するかの確認
let matchingStatus = SecItemCopyMatching(dic as CFDictionary, nil)
if matchingStatus == errSecItemNotFound {
// 保存
itemAddStatus = SecItemAdd(dic as CFDictionary, nil)
} else if matchingStatus == errSecSuccess {
// 更新
itemAddStatus = SecItemUpdate(dic as CFDictionary,[kSecAttrGeneric as String: _data] as CFDictionary)
itemAddStatus = SecItemUpdate(dic as CFDictionary,[kSecAttrAccount as String: _data] as CFDictionary)
itemAddStatus = SecItemUpdate(dic as CFDictionary,[kSecValueData as String: _data] as CFDictionary)
} else {
print("保存失敗")
}
// 保存・更新ステータス確認
if itemAddStatus == errSecSuccess {
print("正常終了")
} else {
print("保存失敗")
}
}
// 取得
func getKeyChain(key: String) -> String? {
let dic: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: key,
kSecReturnData as String: kCFBooleanTrue as Any,
kSecMatchLimit as String : kSecMatchLimitOne]as [String : Any]
var data: AnyObject?
// ポインタ:変数のメモリ上の位置(番地)を格納している
// 変数をポインタに変換してポインタ経由でアクセスする
let matchingStatus = withUnsafeMutablePointer(to: &data){
SecItemCopyMatching(dic as CFDictionary, UnsafeMutablePointer($0))
}
if matchingStatus == errSecSuccess {
print("取得成功")
if let getData = data as? Data,
let getStr = String(data: getData, encoding: .utf8) {
return getStr
}
print("取得失敗: Dataが不正")
return nil
} else {
print("取得失敗")
return nil
}
}
// 削除
func deleteKeyChain() {
// 削除するqueryを設定
let dic: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "test"] // サービス名
if SecItemDelete(dic as CFDictionary) == errSecSuccess {
print("削除成功")
} else {
print("削除失敗")
}
}
}
I'm trying to store an Integer and retrieve it using KeyChain.
This is how I save it:
func SaveNumberOfImagesTaken()
{
let key = "IMAGE_TAKEN"
var taken = 10
let data = NSKeyedArchiver.archivedDataWithRootObject(taken)
let query : [String:AnyObject] = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecValueData as String : data
]
let status : OSStatus = SecItemAdd(query as CFDictionaryRef, nil)
}
This is how I try to retrieve it:
func CheckIfKeyChainValueExitss() -> AnyObject? {
var key = "IMAGE_TAKEN"
let query : [String:AnyObject] = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue,
kSecMatchLimit as String : kSecMatchLimitOne ]
var dataTypeRef :Unmanaged<AnyObject>?
let status: OSStatus = SecItemCopyMatching(query, &dataTypeRef)
if let op = dataTypeRef?.toOpaque() {
let data = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
if let string: AnyObject? = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? AnyObject? {
if key == "IMAGE_TAKEN"
{
return string as! String!
}
else if string == nil
{
return nil
}
}
}
return nil
}
I'm getting the following error:
Could not cast value of type '__NSCFNumber' to 'NSString'
I tried playing with the variables but without success.
I've update Eric's version for Swift 5:
class KeyChain {
class func save(key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ] as [String : Any]
SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil)
}
class func load(key: String) -> Data? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue!,
kSecMatchLimit as String : kSecMatchLimitOne ] as [String : Any]
var dataTypeRef: AnyObject? = nil
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == noErr {
return dataTypeRef as! Data?
} else {
return nil
}
}
class func createUniqueID() -> String {
let uuid: CFUUID = CFUUIDCreate(nil)
let cfStr: CFString = CFUUIDCreateString(nil, uuid)
let swiftString: String = cfStr as String
return swiftString
}
}
extension Data {
init<T>(from value: T) {
var value = value
self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func to<T>(type: T.Type) -> T {
return self.withUnsafeBytes { $0.load(as: T.self) }
}
}
I've update Eric's version for Swift 3:
class KeyChain {
class func save(key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ] as [String : Any]
SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil)
}
class func load(key: String) -> Data? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue,
kSecMatchLimit as String : kSecMatchLimitOne ] as [String : Any]
var dataTypeRef: AnyObject? = nil
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == noErr {
return dataTypeRef as! Data?
} else {
return nil
}
}
class func createUniqueID() -> String {
let uuid: CFUUID = CFUUIDCreate(nil)
let cfStr: CFString = CFUUIDCreateString(nil, uuid)
let swiftString: String = cfStr as String
return swiftString
}
}
extension Data {
init<T>(from value: T) {
var value = value
self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func to<T>(type: T.Type) -> T {
return self.withUnsafeBytes { $0.pointee }
}
}
Example usage:
let int: Int = 555
let data = Data(from: int)
let status = KeyChain.save(key: "MyNumber", data: data)
print("status: ", status)
if let receivedData = KeyChain.load(key: "MyNumber") {
let result = receivedData.to(type: Int.self)
print("result: ", result)
}
Well, I just used out source etc and made my self nice helper :
Enjoy!
class func save(key: String, data: NSData) {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ]
SecItemDelete(query as CFDictionaryRef)
let status: OSStatus = SecItemAdd(query as CFDictionaryRef, nil)
}
class func load(key: String) -> NSData? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue,
kSecMatchLimit as String : kSecMatchLimitOne ]
var dataTypeRef :Unmanaged<AnyObject>?
let status: OSStatus = SecItemCopyMatching(query, &dataTypeRef)
if status == noErr {
return (dataTypeRef!.takeRetainedValue() as! NSData)
} else {
return nil
}
}
class func stringToNSDATA(string : String)->NSData
{
let _Data = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
return _Data!
}
class func NSDATAtoString(data: NSData)->String
{
var returned_string : String = NSString(data: data, encoding: NSUTF8StringEncoding)! as String
return returned_string
}
class func intToNSDATA(r_Integer : Int)->NSData
{
var SavedInt: Int = r_Integer
let _Data = NSData(bytes: &SavedInt, length: sizeof(Int))
return _Data
}
class func NSDATAtoInteger(_Data : NSData) -> Int
{
var RecievedValue : Int = 0
_Data.getBytes(&RecievedValue, length: sizeof(Int))
return RecievedValue
}
class func CreateUniqueID() -> String
{
var uuid: CFUUIDRef = CFUUIDCreate(nil)
var cfStr:CFString = CFUUIDCreateString(nil, uuid)
var nsTypeString = cfStr as NSString
var swiftString:String = nsTypeString as String
return swiftString
}
//EXAMPLES
//
// //Save And Parse Int
// var Int_Data = KeyChain.intToNSDATA(555)
// KeyChain.save("MAMA", data: Int_Data)
// var RecievedDataAfterSave = KeyChain.load("MAMA")
// var NSDataTooInt = KeyChain.NSDATAtoInteger(RecievedDataAfterSave!)
// println(NSDataTooInt)
//
//
// //Save And Parse String
// var string_Data = KeyChain.stringToNSDATA("MANIAK")
// KeyChain.save("ZAHAL", data: string_Data)
// var RecievedDataStringAfterSave = KeyChain.load("ZAHAL")
// var NSDATAtoString = KeyChain.NSDATAtoString(RecievedDataStringAfterSave!)
// println(NSDATAtoString)
This is Sazzad Hissain Khan's answer rewritten for iOS without non-Swifty NS-prefixed attributes and a cleaner code.
import Security
class KeychainService {
class func updatePassword(service: String, account: String, data: String) {
guard let dataFromString = data.data(using: .utf8, allowLossyConversion: false) else {
return
}
let status = SecItemUpdate(modifierQuery(service: service, account: account), [kSecValueData: dataFromString] as CFDictionary)
checkError(status)
}
class func removePassword(service: String, account: String) {
let status = SecItemDelete(modifierQuery(service: service, account: account))
checkError(status)
}
class func savePassword(service: String, account: String, data: String) {
guard let dataFromString = data.data(using: .utf8, allowLossyConversion: false) else {
return
}
let keychainQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account,
kSecValueData: dataFromString]
let status = SecItemAdd(keychainQuery as CFDictionary, nil)
checkError(status)
}
class func loadPassword(service: String, account: String) -> String? {
var dataTypeRef: CFTypeRef?
let status = SecItemCopyMatching(modifierQuery(service: service, account: account), &dataTypeRef)
if status == errSecSuccess,
let retrievedData = dataTypeRef as? Data {
return String(data: retrievedData, encoding: .utf8)
} else {
checkError(status)
return nil
}
}
fileprivate static func modifierQuery(service: String, account: String) -> CFDictionary {
let keychainQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account,
kSecReturnData: kCFBooleanTrue]
return keychainQuery as CFDictionary
}
fileprivate static func checkError(_ status: OSStatus) {
if status != errSecSuccess {
if #available(iOS 11.3, *),
let err = SecCopyErrorMessageString(status, nil) {
print("Operation failed: \(err)")
} else {
print("Operation failed: \(status). Check the error message through https://osstatus.com.")
}
}
}
}
Roi Mulia's answer works very well, here's a version with a few minimal adjustments for Swift 2:
class KeyChain {
class func save(key: String, data: NSData) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ]
SecItemDelete(query as CFDictionaryRef)
return SecItemAdd(query as CFDictionaryRef, nil)
}
class func load(key: String) -> NSData? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue,
kSecMatchLimit as String : kSecMatchLimitOne ]
var dataTypeRef:AnyObject? = nil
let status: OSStatus = SecItemCopyMatching(query, &dataTypeRef)
if status == noErr {
return (dataTypeRef! as! NSData)
} else {
return nil
}
}
class func stringToNSDATA(string : String)->NSData
{
let _Data = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
return _Data!
}
class func NSDATAtoString(data: NSData)->String
{
let returned_string : String = NSString(data: data, encoding: NSUTF8StringEncoding)! as String
return returned_string
}
class func intToNSDATA(r_Integer : Int)->NSData
{
var SavedInt: Int = r_Integer
let _Data = NSData(bytes: &SavedInt, length: sizeof(Int))
return _Data
}
class func NSDATAtoInteger(_Data : NSData) -> Int
{
var RecievedValue : Int = 0
_Data.getBytes(&RecievedValue, length: sizeof(Int))
return RecievedValue
}
class func CreateUniqueID() -> String
{
let uuid: CFUUIDRef = CFUUIDCreate(nil)
let cfStr:CFString = CFUUIDCreateString(nil, uuid)
let nsTypeString = cfStr as NSString
let swiftString:String = nsTypeString as String
return swiftString
}
}
Example usage:
let data = KeyChain.intToNSDATA(555)
let status = KeyChain.save("MyNumber", data: data)
print(status)
if let receivedData = KeyChain.load("MyNumber") {
let result = KeyChain.NSDATAtoInteger(receivedData)
print(result)
}
I tried to make it as simple as possible.
fileprivate class KeychainService {
static func updatePassword(_ password: String, serviceKey: String) {
guard let dataFromString = password.data(using: .utf8) else { return }
let keychainQuery: [CFString : Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrService: serviceKey,
kSecValueData: dataFromString]
SecItemDelete(keychainQuery as CFDictionary)
SecItemAdd(keychainQuery as CFDictionary, nil)
}
static func removePassword(serviceKey: String) {
let keychainQuery: [CFString : Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrService: serviceKey]
SecItemDelete(keychainQuery as CFDictionary)
}
static func loadPassword(serviceKey: String) -> String? {
let keychainQuery: [CFString : Any] = [kSecClass : kSecClassGenericPassword,
kSecAttrService : serviceKey,
kSecReturnData: kCFBooleanTrue,
kSecMatchLimitOne: kSecMatchLimitOne]
var dataTypeRef: AnyObject?
SecItemCopyMatching(keychainQuery as CFDictionary, &dataTypeRef)
guard let retrievedData = dataTypeRef as? Data else { return nil }
return String(data: retrievedData, encoding: .utf8)
}
static func flush() {
let secItemClasses = [kSecClassGenericPassword]
for itemClass in secItemClasses {
let spec: NSDictionary = [kSecClass: itemClass]
SecItemDelete(spec)
}
}
}
Example how to save & retrieve a struct User, a pretty common use-case:
import Security
import UIKit
class KeyChain {
struct User {
let identifier: Int64
let password: String
}
private static let service = "MyService"
static func save(user: User) -> Bool {
let identifier = Data(from: user.identifier)
let password = user.password.data(using: .utf8)!
let query = [kSecClass as String : kSecClassGenericPassword as String,
kSecAttrService as String : service,
kSecAttrAccount as String : identifier,
kSecValueData as String : password]
as [String : Any]
let deleteStatus = SecItemDelete(query as CFDictionary)
if deleteStatus == noErr || deleteStatus == errSecItemNotFound {
return SecItemAdd(query as CFDictionary, nil) == noErr
}
return false
}
static func retrieveUser() -> User? {
let query = [kSecClass as String : kSecClassGenericPassword,
kSecAttrService as String : service,
kSecReturnAttributes as String : kCFBooleanTrue!,
kSecReturnData as String: kCFBooleanTrue!]
as [String : Any]
var result: AnyObject? = nil
let status = SecItemCopyMatching(query as CFDictionary, &result)
if status == noErr,
let dict = result as? [String: Any],
let passwordData = dict[String(kSecValueData)] as? Data,
let password = String(data: passwordData, encoding: .utf8),
let identifier = (dict[String(kSecAttrAccount)] as? Data)?.to(type: Int64.self) {
return User(identifier: identifier, password: password)
} else {
return nil
}
}
}
private extension Data {
init<T>(from value: T) {
var value = value
self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func to<T>(type: T.Type) -> T {
withUnsafeBytes { $0.load(as: T.self) }
}
}
You are storing a number, not a string, so you are getting back an NSNumber, not a string. The exception is pretty clear - you can't downcast an NSNumber to a String - you can use stringValue() to get the string representation of an NSNumber
if let op = dataTypeRef?.toOpaque() {
let data = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
if let string: AnyObject? = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? AnyObject? {
if key == "IMAGE_TAKEN"
{
return string.stringValue() as! String!
}
else if string == nil
{
return nil
}
}
}
Whats the best way to generate a RSA key pair in iOS and get them as String. I have seen couple of libs which can generate but i can't get the Private Key as String. Does anyone know a lib or way to get the Private key in String?
Currently i generate the Key pair this way
var statusCode: OSStatus
var publicKey: SecKey?
var privateKey: SecKey?
let publicKeyAttr: [NSObject: NSObject] = [kSecAttrIsPermanent:true as NSObject, kSecAttrApplicationTag:"publicTag" as NSObject]
let privateKeyAttr: [NSObject: NSObject] = [kSecAttrIsPermanent:true as NSObject, kSecAttrApplicationTag:"privateTag" as NSObject]
var keyPairAttr = [NSObject: NSObject]()
keyPairAttr[kSecAttrKeyType] = kSecAttrKeyTypeRSA
keyPairAttr[kSecAttrKeySizeInBits] = 2048 as NSObject?
keyPairAttr[kSecPublicKeyAttrs] = publicKeyAttr as NSObject?
keyPairAttr[kSecPrivateKeyAttrs] = privateKeyAttr as NSObject?
statusCode = SecKeyGeneratePair(keyPairAttr as CFDictionary, &publicKey, &privateKey)
if statusCode == noErr && publicKey != nil && privateKey != nil {
print(publicKey!)
print(privateKey!)
} else {
print("Error generating key pair: \(statusCode)")
}
Its in SecKey. How to convert them to String? or is there another way to do it ?
Here's the code using SecItemCopyMatching:
let PublicKeyTag = "publicTag"
let PrivateKeyTag = "privateTag"
let publicKeyAttr: [NSString: Any] = [
kSecAttrIsPermanent: NSNumber(value: true),
kSecAttrApplicationTag: PublicKeyTag
]
let privateKeyAttr: [NSString: Any] = [
kSecAttrIsPermanent: NSNumber(value: true),
kSecAttrApplicationTag: PrivateKeyTag
]
let keyPairAttr: [NSString: Any] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits: 2048 as NSObject,
kSecPublicKeyAttrs: publicKeyAttr,
kSecPrivateKeyAttrs: privateKeyAttr
]
var publicKey: SecKey?
var privateKey: SecKey?
var statusCode: OSStatus
statusCode = SecKeyGeneratePair(keyPairAttr as CFDictionary, &publicKey, &privateKey)
if statusCode == noErr && publicKey != nil && privateKey != nil {
print(publicKey!)
print(privateKey!)
} else {
print("Error generating key pair: \(statusCode)")
}
var dataPtr: AnyObject?
let query: [NSString: Any] = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: PrivateKeyTag,
kSecReturnData: NSNumber(value: true)
]
statusCode = SecItemCopyMatching(query as CFDictionary, &dataPtr)
let privateKeyData = dataPtr as! Data
let privateKeyString = privateKeyData.base64EncodedString(options: [])
print(privateKeyString)
privateKeyData seems to contain ASN.1 encoded information. The final result is Base64 encoded.
Try the below method if it helps anyone.
Add the below method in your code with input parameter of type SecKey.
func secKeyToString(key:SecKey) {
var error:Unmanaged<CFError>?
if let cfData = SecKeyCopyExternalRepresentation(key, &error) {
let base64KeyString = (cfData as Data).base64EncodedString()
print("Sec key in string:\(base64KeyString)")
}
}
You can also create an extension to the SecKey class with the same codebase and return the base 64 encoded string as below:
extension SecKey {
func toString()-> String{
var error:Unmanaged<CFError>?
let cfData = SecKeyCopyExternalRepresentation(self, &error)
let base64KeyString = (cfData as! Data).base64EncodedString()
return base64KeyString
}
}
Use the method as following:
//publicSecKey is your public key in SecKey format
let keyInStringFormat = publicSecKey.toString()
print("public key in string:\(keyInStringFormat)")
I'm attempting to do the following in my iOS application:
Generate a key pair using SecKeyGeneratePair, storing the private key in the Secure Enclave
Sign some data using the private key
It works if I hang on to the private key reference when keys are first generated, but does not work if I attempt to retrieve the reference from the Keychain after discarding the initial pointer.
The keys are generated like this:
func generateKeyPair() -> Bool {
if let access = SecAccessControlCreateWithFlags(nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.userPresence, .privateKeyUsage],
nil) {
let privateKeyAttr = [kSecAttrIsPermanent : 1,
kSecAttrApplicationTag : privateTag,
kSecAttrAccessControl as String: access
] as NSDictionary
let publicKeyAttr = [kSecAttrIsPermanent : 0,
kSecAttrApplicationTag : publicTag
] as NSDictionary
let keyPairAttr = [kSecAttrKeySizeInBits : 256,
kSecAttrKeyType : kSecAttrKeyTypeEC,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs : privateKeyAttr,
kSecPublicKeyAttrs : publicKeyAttr] as NSDictionary
let err = SecKeyGeneratePair(keyPairAttr, &publicKey, &privateKey)
return err == noErr
}
The signing method is below:
func signData(plainText: Data) -> NSData? {
guard privateKey != nil else {
print("Private key unavailable")
return nil
}
let digestToSign = self.sha1DigestForData(data: plainText as NSData) as Data
let signature = UnsafeMutablePointer<UInt8>.allocate(capacity: 128)
var signatureLength = 128
let err = SecKeyRawSign(privateKey!,
.PKCS1SHA1,
[UInt8](digestToSign),
Int(CC_SHA1_DIGEST_LENGTH),
signature,
&signatureLength)
print("Signature status: \(err)")
let sigData = NSData(bytes: signature, length: Int(signatureLength))
return sigData
}
func sha1DigestForData(data: NSData) -> NSData {
let len = Int(CC_SHA1_DIGEST_LENGTH)
let digest = UnsafeMutablePointer<UInt8>.allocate(capacity: len)
CC_SHA1(data.bytes, CC_LONG(data.length), digest)
return NSData(bytesNoCopy: UnsafeMutableRawPointer(digest), length: len)
}
This asks for my fingerprint and works flawlessly. I then use another method to get the key reference from the Keychain:
func getPrivateKeyRef() -> SecKey? {
let parameters = [
kSecClass as String: kSecClassKey,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrApplicationTag as String: privateTag,
kSecReturnRef as String: true,
] as [String : Any]
var ref: AnyObject?
let status = SecItemCopyMatching(parameters as CFDictionary, &ref)
print("Get key status: \(status)")
if status == errSecSuccess { return ref as! SecKey? } else { return nil }
}
The SecItemCopyMatching returns a success status, but attempting to use the resulting SecKey item as a private key in SecKeyRawSign results in error -25293 Authorization/Authentication failed. This status only appears after I provide my fingerprint, so the actual fingerprint verification succeeds, but the key somehow remains unusable.
What is the correct way to use a key stored in Secure Enclave to sign data?
Looks like using kSecAttrLabel attribute is necessary for correct fetching of the key. It must be specified when generating the keys, and when fetching them through SecItemCopyMatching.
My working solution for fetching the private key:
func getPrivateKey() -> SecKey? {
let parameters = [
kSecClass as String: kSecClassKey,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrApplicationTag as String : "privateTag",
kSecAttrLabel as String : "privateTag",
kSecReturnRef as String: true,
] as [String : Any]
var ref: AnyObject?
let status = SecItemCopyMatching(parameters as CFDictionary, &ref)
if status == errSecSuccess {
return (ref as! SecKey)
}
return nil
}
Where the key pair was generated like this:
var publicKey:SecKey?
var privateKey:SecKey?
if let access = SecAccessControlCreateWithFlags(nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.userPresence, .privateKeyUsage],
nil) {
let privateKeyAttr = [kSecAttrIsPermanent : 1,
kSecAttrApplicationTag as String : "privateTag",
kSecAttrLabel as String : "privateTag",
kSecAttrAccessControl as String: access
] as NSDictionary
let publicKeyAttr = [kSecAttrIsPermanent : 0,
kSecAttrApplicationTag as String : "publicTag",
kSecAttrLabel as String : "publicTag",
] as NSDictionary
// only 256 bit EC keys are supported in the Secure Enclave
let keyPairAttr = [kSecAttrKeySizeInBits : 256,
kSecAttrKeyType : kSecAttrKeyTypeEC,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs : privateKeyAttr,
kSecPublicKeyAttrs : publicKeyAttr] as NSDictionary
let err = SecKeyGeneratePair(keyPairAttr, &publicKey, &privateKey)
I encountered an issue with creating SecKey from NSData. Basically my client-server communication is based on signature created with private key and verified on the server with public key.
I am implementing session transfer between two devices and in order to continue communication I need those keys to be transferred as well. I am converting SecKey to NSData and sending it via bluetooth, but on other side I cannot convert NSData to SecKey back to use encryption.
Could you help please?
More complete example (swift 4, iOS 10+) - assuming you have a Base64 encoded string. Note that the other side of the connection needs to also be creating key payloads using the same format (i.e. RSA - PKCS #1, also verify key size ). This function handles public or private keys (security caveats omitted for brevity).
// Extract secKey from encoded string - defaults to extracting public keys
func decodeSecKeyFromBase64(encodedKey: String, isPrivate: Bool = false) -> SecKey? {
var keyClass = kSecAttrKeyClassPublic
if isPrivate {
keyClass = kSecAttrKeyClassPrivate
}
let attributes: [String:Any] =
[
kSecAttrKeyClass as String: keyClass,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
]
guard let secKeyData = Data.init(base64Encoded: encodedKey) else {
print("Error: invalid encodedKey, cannot extract data")
return nil
}
guard let secKey = SecKeyCreateWithData(secKeyData as CFData, attributes as CFDictionary, nil) else {
print("Error: Problem in SecKeyCreateWithData()")
return nil
}
return secKey
}
Starting from iOS 10 you can use the following code:
let attributes: [String:Any] = [
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: blockSize,
]
secKey = SecKeyCreateWithData(secKeyData as CFData, attributes as CFDictionary, nil)
what I use with success ...
func obtainKeyData(tag: String) -> NSData? {
var keyRef: AnyObject?
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecReturnData): kCFBooleanTrue as CFBoolean,
String(kSecClass): kSecClassKey as CFStringRef,
String(kSecAttrApplicationTag): tag as CFStringRef,
]
let result: NSData?
switch SecItemCopyMatching(query, &keyRef) {
case noErr:
result = keyRef as? NSData
default:
result = nil
}
return result
}
func insertPublicKey(publicTag: String, data: NSData) -> SecKeyRef? {
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecClass): kSecClassKey as CFStringRef,
String(kSecAttrApplicationTag): publicTag as CFStringRef,
String(kSecValueData): data as CFDataRef,
String(kSecReturnPersistentRef): true as CFBooleanRef]
var persistentRef: AnyObject?
let status = SecItemAdd(query, &persistentRef)
if status != noErr && status != errSecDuplicateItem {
return nil
}
return obtainKey(publicTag)
}
func obtainKey(tag: String) -> SecKey? {
var keyRef: AnyObject?
let query: Dictionary<String, AnyObject> = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecReturnRef): kCFBooleanTrue as CFBoolean,
String(kSecClass): kSecClassKey as CFStringRef,
String(kSecAttrApplicationTag): tag as CFStringRef,
]
let status = SecItemCopyMatching(query, &keyRef)
switch status {
case noErr:
if let ref = keyRef {
return (ref as! SecKeyRef)
}
default:
break
}
return nil
}
There is no easy way to transfer private part of the key pair( it is possible, but try to avoid it )