How to generate RSA private Key in Swift? - swift

I have encrypted a text with a public key generated using modulus and exponent, I followed that link : https://meniny.cn/posts/RSA_public_key_with_modulus_and_exponent/
I want to decrypt that text with a private key generated using another modulus and exponent.I didn't find a function in Swift or Objective-C that solve this issue.
So I use a Java code to get the private key as String and generate also a pem file.
Here is my code :
let PRIVATE_KEY = "MIGxAgEAMA0GCSqGSIb3DQEBAQUABIGcMIGZAgEAAkBXIKDI5NbyZd/d5tO6djSv\rt8GDc7soyNaqSqZq/w9A/zxiZTA0uwnvYv9E+OXKS9yjPCqpu9d1ELzxQxU9KRFD\rAgEAAkAYEBbX5PvIboJpkrqfIM5kSWfUmj3ygaVn2r4jhtX7qS8+0v09fwifoeMP\r5TgmB2B8+47n8+MQ55/cKbMs2QpBAgEAAgEAAgEAAgEAAgEA\r"
let data = PRIVATE_KEY.data(using: String.Encoding.utf8)!
let priv = data.base64EncodedString()
let keyData = Data(base64Encoded: priv)!
let dict = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits : NSNumber(value : 128),
] as [CFString : Any]
let key = SecKeyCreateWithData(keyData as NSData, dict as NSDictionary, nil)
let blockSize = SecKeyGetBlockSize(key!)
var encrypted = [UInt8](repeating: 0, count: blockSize)
var encSize = blockSize
let status = SecKeyDecrypt(key!,
SecPadding.PKCS1, x,
x.count, &encrypted,
&encSize)
let decData = NSData(bytes: &encrypted, length: encrypted.count)
let decString = decData.base64EncodedString(options: NSData.Base64EncodingOptions())
I always get nil for key value.
I tried to find a solution but I am unable to get the suitable private key to decrypt correctly my text.
Any help please?

Related

Problem with Steam RSA Login implementation in Swift 5.0

So when logging in to steam, I need my password encrypted with RSA public key they provided. But when I encrypt with SecKeyEncryptedData and sent it for authentication, it says my password is incorrect. I think maybe it's a problem with the format of encryption but I cannot figure it out. Please help me with that.
static func encrypt(string: String, mod: String, exp: String) -> String? {
let keyString = self.rsaPublicKeyder(mod: mod, exp: exp)
guard let data = Data(base64Encoded: keyString) else { return nil }
var attributes: CFDictionary {
return [kSecAttrKeyType : kSecAttrKeyTypeRSA,
kSecAttrKeyClass : kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits : 2048,
kSecReturnPersistentRef : kCFBooleanTrue!] as CFDictionary
}
var error: Unmanaged<CFError>? = nil
guard let secKey = SecKeyCreateWithData(data as CFData, attributes, &error) else {
print(error.debugDescription)
return nil
}
guard let result = SecKeyCreateEncryptedData(secKey, SecKeyAlgorithm.rsaEncryptionPKCS1, string.data(using: .utf8)! as CFData, &error) else{
print(error.debugDescription)
return nil
}
return (result as Data).base64EncodedString()
}
}
The self.rsaPublicKeyder function's role is to convert the mod and exp format to PKCS#8 DER format and it can import with SecKeyCreateWithData. And I tried the result Der in cyberchef which seems no problem.
I tried using BigInt library to encrypt myself, but still failed. How????
class RSA{
static func encrypt(string: String, mod: String, exp: String) -> String
{
let secret = pkcs1pad2(data: string.data(using: .utf8)!, keySize: mod.count / 2)!
return secret.power(BigUInt(exp, radix: 16)!, modulus: BigUInt(mod, radix: 16)!).serialize().base64EncodedString()
}
static func pkcs1pad2(data: Data, keySize: Int) -> BigUInt?{
if (keySize < data.count + 11){
return nil;
}
var rndData: [UInt8] = [UInt8](repeating: 0, count: keySize - 3 - data.count)
let status = SecRandomCopyBytes(kSecRandomDefault, rndData.count, &rndData)
for i in 0..<rndData.count{
if rndData[i] == 0{
rndData[i] = UInt8(i+1)
}
}
guard status == errSecSuccess else{
return nil
}
return BigUInt(Data([0x00, 0x02]) + Data(rndData) + Data([0x00]) + data)
}
}
I have cost a bunch of time on this problem and I still don't know which part of my code is doing wrong. I have put all codes on Github. If you can help me with it I'll be so grateful.
https://github.com/MTAwsl/iAuth/tree/dev
I tried to debug myself using Fiddler and I found the answer.
Firstly, when a base64 string is transferred through HTTP GET method, it needs to be encoded by "%" encoding. So, String.addingPercentEncoding should be called to encode the string with proper encoding. But in CharacterSet.urlHostAllowed set, it did not include the "+" character so when the server decoding the data from base64, it treats "+" as space, which is definitely not what we wanted. I add the extension to String module and it solved the problem. Besides, yeah, both the BigInt and SecKey method works. There's nothing about the RSA encryption.
extension String{
var encoded: String? {
var urlB64Encoded: CharacterSet = .urlHostAllowed
urlB64Encoded.remove(charactersIn: "+")
return self.addingPercentEncoding(withAllowedCharacters: urlB64Encoded)
}
}

CryptoSwift - Getting nil when using makeEncryptor() for string of less than 16 bytes in iOS

I am using cryptoSwift library for encryption and decryption. But it's working with only string 16 bytes. If i am passing small string or less than 16 bytes then getting nil result.
I am using Incremental operations use instance of Cryptor and encrypt/decrypt one part at a time.
Please help me here, is there anything which i am doing wrong.
Thanks in advance.
func encAndDec(){
do {
// Encryption start
let data = Data.init(base64Encoded: "12345678901234567890123456789012".base64Encoded()!)
let iv : Array<UInt8> = [0,0,0,0,0,0,0,0,0,0,0,0]
let nIv = Data(iv)
let gcmEnc = GCM(iv: nIv.bytes, mode: .detached)
var enc = try? AES(key: data!.bytes, blockMode: gcmEnc, padding: .noPadding).makeEncryptor()
let arrStr = ["My name is tarun"] // Working
//let arrStr = ["tarun"] // Not working for this string
var ciphertext = Array<UInt8>()
for txt in arrStr{
let ciperText = try? enc?.update(withBytes: Array(txt.utf8)) // Getting nil for small string.
ciphertext.append(contentsOf: ciperText!)
}
var res = try? enc?.finish()
gcmEnc.authenticationTag = self.randomGenerateBytes(count: 16)?.bytes
res?.append(contentsOf: (gcmEnc.authenticationTag)!)
let cipherData = Data(ciphertext) + Data(res!)
let strEnc = String(decoding: cipherData, as: UTF8.self)
print(strEnc)
// Decryption start from here
do {
let gcmDec = GCM.init(iv: nIv.bytes, additionalAuthenticatedData: nil, tagLength: 16, mode: .detached)
var aesDec = try! AES(key: data!.bytes, blockMode: gcmDec, padding: .noPadding).makeDecryptor()
let tag_length = 16
let encData = cipherData.subdata(in: 0..<cipherData.count - tag_length)
let tag = cipherData.subdata(in: encData.count ..< cipherData.count)
let decData = try? aesDec.update(withBytes: encData.bytes) //Getting nil here for small string
let strData = String(decoding: decData!, as: UTF8.self)
print(strData)
do{
var res = try? aesDec.finish(withBytes: tag.bytes)
res?.append(contentsOf: tag)
}catch{
}
} catch {
// failed
}
}
}
func randomGenerateBytes(count: Int) -> Data? {
let bytes = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: 1)
defer { bytes.deallocate() }
let status = CCRandomGenerateBytes(bytes, count)
guard status == kCCSuccess else { return nil }
return Data(bytes: bytes, count: count)
}
There's nothing wrong with aes-256-gcm implementation in CryptoSwift, as some of the commenters have suggested, you just have some bugs in your code. Hopefully the following will help you out.
I'm just going to call it GCM below for brevity.
GCM encryption takes as input the plaintext, a key, and an initialization vector and produces ciphertext and an authentication tag. In your code, you set the authentication tag to random bytes, overwriting the authentication tag.
I think it's a bit clearer if you break your code up into some functions, each with a clearly defined purpose. I also stripped away some of the conversions from Data to and from [UInt8] for clarity.
Here's what the encryption function would look like:
func enc(plainText: [String], key: [UInt8], iv: [UInt8]) throws -> (cipherText: [UInt8], authenticationTag: [UInt8]?)
{
let gcmEnc = GCM(iv: iv, mode: .detached)
var enc = try AES(key: key, blockMode: gcmEnc, padding: .noPadding).makeEncryptor()
var ciphertext = Array<UInt8>()
for txt in plainText {
ciphertext += try enc.update(withBytes: Array(txt.utf8))
}
ciphertext += try enc.finish()
return (ciphertext, gcmEnc.authenticationTag)
}
When you're decrypting GCM you need to pass in the ciphertext, key, initialization vector and the authentication tag. That would look like this:
func dec(cipherText: [UInt8], authenticationTag: [UInt8]?, key: [UInt8], iv: [UInt8]) throws -> [UInt8]? {
let tagLength = authenticationTag?.count ?? 0
let gcmDec = GCM.init(iv: iv, additionalAuthenticatedData: nil, tagLength: tagLength, mode: .detached)
gcmDec.authenticationTag = authenticationTag
var aesDec = try AES(key: key, blockMode: gcmDec, padding: .noPadding).makeDecryptor()
var decData = try aesDec.update(withBytes: cipherText)
decData += try aesDec.finish()
return decData
}
In both cases, you need to make sure that you append the output of the finish call to the ciphertext or plaintext. This is particularly important with small amounts of data as the update method may produce nothing!
With these two functions written you can rewrite your test function as follows:
func encAndDec(){
do {
guard let key = Data.init(base64Encoded: "12345678901234567890123456789012".base64Encoded()!)
else {
fatalError("Failed to create key")
}
let iv : Array<UInt8> = [0,0,0,0,
0,0,0,0,
0,0,0,0]
//let arrStr = ["My name is tarun"] // Working
let arrStr = ["tarun"] // Not working for this string
let (cipherText, authenticationTag) = try enc(plainText: arrStr, key: key.bytes, iv: iv)
guard let decrypedPlainText = try dec(cipherText: cipherText,
authenticationTag: authenticationTag, key: key.bytes, iv: iv) else {
fatalError("Decryption return nil")
}
guard let decryptedString = String(bytes: decrypedPlainText, encoding: .utf8) else {
fatalError("Failed to convert plaintext to string using UTF8 encoding.")
}
print("Decrypted Plaintext: \(decryptedString)")
}
catch let e {
print("EXCEPTION: \(e)")
}
}
If you run this you'll find it produces the expected output.
The complete example code can be found at: https://gist.github.com/iosdevzone/45456d2911bf2422bc4a6898876ba0ab
I don't believe GCM requires PADDING. Here is an example pretty much straight from the NODE.JS documentation that works fine and does not use padding. The line below will show the Ciphertext length is 5
I have done the same with Ruby, Java, Go, and others and none require padding or the input value to be a multiple of 16 bytes like the Swift library seems to require. Anyone else help confirm this is a bug in Swift implementation of GCM?
const crypto = require('crypto');
const key = '12345678901234567890123456789012';
const iv = '000000000000'
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const plaintext = 'Hello';
const ciphertext = cipher.update(plaintext, 'utf8');
**console.log("ciphertext length %d", ciphertext.length)**
cipher.final();
const tag = cipher.getAuthTag();
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
const receivedPlaintext = decipher.update(ciphertext, null, 'utf8');
try {
decipher.final();
} catch (err) {
console.error('Authentication failed!');
return;
}
console.log(receivedPlaintext);

SecKey (PCKS1) to Base64 (PCKS8) from p12

I have looked at other answers on StackOverflow and I didn't find what I was looking for (IOS11). I have a SecKey (privateKey) that when I print:
SecKeyRef algorithm id: 1, key type: RSAPrivateKey, version: 4, block size: 2048 bits, addr: 0x1d0223f60
I have tried to convert it to Data and from there to Base64
let password = "1234"
let p12data = NSData(contentsOfFile: urls[0].path)!
var importResult: CFArray? = nil
let err = SecPKCS12Import(p12data as NSData,[kSecImportExportPassphrase as String: password] as NSDictionary,&importResult )
//GET IDENTITY
let identityDictionaries = importResult as! [[String:Any]]
var privateKey: SecKey?
//GET SECKEY
SecIdentityCopyPrivateKey(identityDictionaries[0][kSecImportItemIdentity as String] as! SecIdentity, &privateKey);
print(privateKey)
//Return data in PCKS1
let dataPrivate = SecKeyCopyExternalRepresentation(privateKey!, nil)
let b64Key:Data = dataPrivate as! Data
print(b64Key.base64EncodedString(options: .lineLength64Characters))
Apple documentation says that SecKeyCopyExternalRepresentation return PCKS1 data (https://developer.apple.com/documentation/security/1643698-seckeycopyexternalrepresentation) but I need PCKS8.
The result is a base64 from PCKS1 but I have to send it to a JAVA server to be processed and expected format is base64 from PCKS8.
is there a way to convert from PCKS1 to PCKS8 and then to base64?
I have found this article: https://blog.wingsofhermes.org/?p=42 that more or less is what I want but its in objective-c and I have not been able to convert it to swift

How to cast decrypted UInt8 to String?

I am using CryptoSwift to encrypt data. I am learning how to use it however I cannot get past the first basic tutorial. I am unable to convert the encrypted data back to a String - which kind of defeats the purpose of encrypting it in the first place if I cannot legibly decrypt the data
Code:
let string = "Hi. This is Atlas"
let input: [UInt8] = Array(string.utf8)
print(input)
let key: [UInt8] = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]
let iv: [UInt8] = AES.randomIV(AES.blockSize)
do {
let encryptedBytes: [UInt8] = try AES(key: key, iv: iv, blockMode: .CBC).encrypt(input, padding: PKCS7())
print(encryptedBytes)
let decrypted: [UInt8] = try AES(key: key, iv: iv, blockMode: .CBC).decrypt(encryptedBytes, padding: PKCS7())
print(decrypted) // << need to convert this array of byted to a string (should be equal to original input)
} catch {
} catch {
}
Thank you for the help
You'll want Foundation to decode the UTF8 for you since there's no way to generate a String.UTF8View directly. So convert to NSData first.
let decrypted: [UInt8] = [0x48, 0x65, 0x6c, 0x6c, 0x6f]
let data = NSData(bytes: decrypted, length: decrypted.count)
let str = String(data: data, encoding: NSUTF8StringEncoding)
If you want to do it without Foundation, you can, but it's a little work. You have to manage the decoding yourself.
extension String {
init?(utf8Bytes: [UInt8]) {
var decoder = UTF8()
var g = utf8Bytes.generate()
var characters: [Character] = []
LOOP:
while true {
let result = decoder.decode(&g)
switch result {
case .Result(let scalar): characters.append(Character(scalar))
case .EmptyInput: break LOOP
case .Error: return nil
}
}
self.init(characters)
}
}
let unicode = String(utf8Bytes: bytes)
(I'm very surprised that this isn't built into Swift stdlib since it's so common and can be quickly built out of other parts of Swift stdlib. Often when that's the case, there's a reason that I'm just not aware of yet, so there may be some subtle problem with my approach here.)
let stringDecrypted = String(decrypted.map { Character(UnicodeScalar($0)) })
So it maps each UInt8 to UnicodeScalar and then to Character. After that it uses String's initializer to create String from array of Characters.

How do I export a public key <SecKey> that was generated using SecKeyGeneratePair to be used on a server?

I generated a keeper using SecKeyGeneratePair.
var publicKeyPtr, privateKeyPtr: Unmanaged<SecKey>?
let publicKeyParameters: [String: AnyObject] = [
kSecAttrIsPermanent: true,
kSecAttrApplicationTag: "com.example.site.public"
]
let privateKeyParameters: [String: AnyObject] = [
kSecAttrIsPermanent: true,
kSecAttrApplicationTag: "com.example.site.private"
]
let parameters: [String: AnyObject] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits: 2048,
kSecPublicKeyAttrs.takeUnretainedValue() as String: publicKeyParameters,
kSecPrivateKeyAttrs.takeUnretainedValue() as String: privateKeyParameters
]
let result = SecKeyGeneratePair(parameters, &publicKeyPtr, &privateKeyPtr)
let publicKey = publicKeyPtr!.takeRetainedValue()
let privateKey = privateKeyPtr!.takeRetainedValue()
let blockSize = SecKeyGetBlockSize(publicKey)
If I print out the publicKey I can see the modulus, which I'm pretty sure is what I need:
publicKey: <SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 3, block size: 2048 bits, exponent: {hex: 10001, decimal: 65537}, modulus: B2A7BD90C909F8084AD5B34040ABDAF7D1A6AFBADB35F3B6AB5CDDAB473449B0F175DEA32A7476F339D98F4AB3716AA2C1476D4009A80574B984DDFA1EF1A2550E48C46791CEFBFC39EF281049AA74E4C734C3B2A7B3F621B8A41F8B6689C4978696690D4EF9FFF0F90DB85C8ECBCF721FB7652AD7B337880A09D97EA736008C3ADBB72223F18C522C0C0889B05122561042D8637D1CBEF8F9F5AE88CDC43E411AA217E2A81C2D812B46D01C3BDC2799DFF3EAD46BB092A566E18EE94F63C4690ECE806B993FDDAC3159BE2098C2428F24969C109E221D8F066BEE3530848DE328D888B4C7E701435EACB116F97BB77B9379EF818B4D280890262EE678B92705, addr: 0x144841a00>
But I cannot figure out how to export the key so I can send it to my server for use there.
From my understanding. A SecKey is stored in Keychain and is a pointer to it, the block size is the length of the key in the memory. So in theory I can extract it as NSData and then convert it to something my server can read. In theory I think that will work, I've hit a wall trying to do that in practice. All help will be greatly appreciated.
SecItemCopyMatching is for you:
var dataPtr:Unmanaged<AnyObject>?
let query: [String:AnyObject] = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: "com.example.site.public",
kSecReturnData: kCFBooleanTrue
]
let qResult = SecItemCopyMatching(query, &dataPtr)
// error handling with `qResult` ...
let publicKeyData = dataPtr!.takeRetainedValue() as NSData
// convert to Base64 string
let base64PublicKey = publicKeyData.base64EncodedStringWithOptions(nil)
Swift 4:
var dataPtr:CFTypeRef?
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: "com.example.site.public",
kSecReturnData as String: true
]
let qResult = SecItemCopyMatching(query as CFDictionary, &dataPtr)
// error handling with `qResult` ...
let data = dataPtr as! Data
let base64PublicKey = data.base64EncodedString()
Note that the size of the data is 270, not the same as block size of the key. See this question on the crypto.stackexchange.com.