I am building an iPhone app which gets a decrypted string via JSON from a PHP backend.
In PHP I am encrypting the string like this:
$encrypt_method = "AES-256-CBC";
$secret_key = 'This is my secret key';
$secret_iv = 'This is my secret iv';
// hash
$key = hash('sha256', $secret_key);
// iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
$iv = substr(hash('sha256', $secret_iv), 0, 16);
if( $action == 'encrypt' ) {
$output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
$output = base64_encode($output);
}
In Objective C I tried to decrypt this string with BBEAS: https://github.com/benoitsan/BBAES
This is the code I have got in Objective C:
NSData* salt = [BBAES IVFromString:#"This is my secret iv"];
NSData *key = [BBAES keyBySaltingPassword:#"This is my secret key" salt:salt keySize:BBAESKeySize256 numberOfIterations:BBAESPBKDF2DefaultIterationsCount];
NSData *decryptedMessage = [BBAES decryptedDataFromString:#"RlVnd01XOE5teTNseDFGQ3JScVhkQT09" IV:salt key:key];
NSLog(#"Decrypted message: %#", decryptedMessage);
The log only shows a null object now.
I have found a duplicate post for C#: How to decrypt an AES-256-CBC encrypted string
EDIT:
Lets say that i can adjust the encoding in PHP. How should I encrypt the string in PHP to be decrypted in Objective C?
You are not doing the same thing in PHP as in iOS. I am not familiar with this BBAES framework, but what you seem to have is a password from which you are generating a 256 bit AES key using PBKDF key derivation, and using that to decrypt the data.
However, in PHP you are hashing your password and using it to encrypt your data, so you are probably using different AES keys for encryption and decryption. And I am not sure that IVs match either.
What you should do is:
In PHP, generate a random 16 byte IV for every encryption you do and use PBKDF key derivation to generate the 256 bit AES key from your password. Keep in mind that the salt and the number of iterations have to be the same in both PHP and iOS. After the encryption, append the IV to the encrypted data and send it.
In iOS, extract the IV from the received ciphertext (the last 16 bytes), generate the AES key from your password the same way you did before using the same salt and number of iterations, and decrypt the data (without the 16 byte IV at the end)
Edit:
As #Zaph pointed out, I forgot to mention that you should use also the same type of padding. BBAES seem to use PKCS7 padding.
To decrypt in Objective C you can use Apples's version of the CommonCrypto C library. It has a man page and there are already several posts that show decryption examples on Stack Overflow for example:
Determine if key is incorrect with CCCrypt kCCOptionPKCS7Padding-Objective C
which comes from the tutorial here:
http://robnapier.net/aes-commoncrypto
This also really helped me:
CCCrypt decrypting in AES CBC works even without IV
If you have trouble getting it working post some code.
Related
I have tried using three different libraries to AES-encrypt a string.
When I use the tool found here I get the following results:
Input: "Test"
key: "MyEncryptionKey1MyEncryptionKey1" (256 Bit)
ECB mode.
this gives me the output Cidor8Ph7pZqPw0x2AwIKw==
But when i'm using the libraries in Swift I get different results.
Using RNCryptor
When i'm using RNcryptor i'm using the following code:
class func encryptMessage(message: String) throws -> String {
guard let messageData = message.data(using: .utf8) else { return message }
let cipherData = RNCryptor.encrypt(data: messageData, withPassword: key)
return cipherData.base64EncodedString()
}
output:
AwF8a+HziYkO4iHdcI3jY8p9QAY461DVgkjkYUFMkuh4A2a8FCfa4RgS9Z37QhJGxIL0Q20RE3BL4nmLQVFOfZmBpj8l0wj9YZgqZmrkxRFYQQ==
Using AESCrypt
When i'm using RNcryptor i'm using the following code:
class func encryptMessageAES(message: String) -> String{
guard let encryptedData = AESCrypt.encrypt(message, password: key) else { return message }
return encryptedData
}
Output:
T5/mR8UT/EXeUobPTLhcFA==
Also if i'm using CryptoSwift i'm getting a third result. My co-worker who does Android always gets the same result - matching the web tool.
I am totally new to encryption and I see that i'm doing something wrong. But I can't really realize what. I should also mention that this encryption is only used to not have chat messages in raw strings showing in Firebase, for those who have access to the database.
The definition of AES is quite precise and when things don't work between different implementations it's often due various things build on top of AES. The AES algorithm itself always operates on binary data. The data you encrypt needs to be binary. The key you use to encrypt with, needs to be binary and If an IV is in play, it also needs to be binary.
In all implementations where you provide data to the implementation that are not binary, a choice have been made on how that data is transformed into a format that can be used with AES. Sometimes these transformations are just simple data conversions like hex or base64 decoding, but other times whole new concepts are in play, like deriving encryption keys from passwords.
All of your three examples uses text as input for the Key, and each implementation have made some choice on how to support that.
The first page you link to has chosen to just interpret an ASCII string as a binary key. This is a terrible choice as it (in addition to being incompatible with everything else) effectively eliminates 1-2 bits per bytes of the key, reducing the strength considerable.
In the RNCryptor example you specify the key with withPassword: key. Here the RNCryptor team have chosen to use a PBKDF2 key deriving function to make an actual AES key. This solves a different usecase, where you have an potential weak password that needs stretching to be secure for encryption. If you have an actual key, this is not the way to go.
In the case of AESCrypt you also seems to be providing a password as input. It's not clear how that would be transformed to an actual key.
There is one more value which you’ll have to set in AES which is iv. So try to find that iv in all three libraries. And also try to set same value for iv. And then you may be able to get same results from all libraries.
I need to make an encryptor and decryptor which reads a file with text and encrypts it and then generates a txt file with the encryption.
Then you must decrypt that same txt file
So far I managed to encrypt the text and put it in the file but I still can not decrypt the text that is inside this.
The encrypt work without the files
**The problem starts here**
#Decrypt
data = ''
File.open('text2.txt','r') do |archivo|
while line = archivo.gets
data += line
end
end
encrypted = data
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
cipher.decrypt
cipher.key = key
cipher.iv = iv
# and decrypt it
decrypted = cipher.update(encrypted)
decrypted << cipher.final
puts "decrypted: #{decrypted}\n"
The error is : encrypt.rb:48:in final': wrong final block length (OpenSSL::Cipher::CipherError)
from encrypt.rb:48:in'
The issue is in the block where you write out the encryption to the file text2.txt. You're using archivo.puts, but you want to use archivo.write. puts will append a newline character at the end of each line, so you're writing encrypted + "\n" to the file. When you try to decrypt it, that extra newline character messes it up. If you use write, it just writes exactly what you give it without the extra character.
What you want to do instead:
File.open('text2.txt', 'w') do |archivo|
archivo.write encrypted
end
Or even better yet:
File.write('text2.txt', encrypted)
I am getting decryption error on sever when I used SwiftyRSA to encrypt a text using publicKey on client.
I have referred this
RSA: encrypt in iOS, decrypt in Java but this is for SHA1.
I am using https://github.com/TakeScoop/SwiftyRSA library.
kSecPaddingOAEP and RSA/NONE/OAEPWithSHA1AndMGF1Padding works. But how Can I make it work for RSA/NONE/OAEPWithSHA256AndMGF1Padding
let str = "Clear Text"
let clear = try ClearMessage(string: str, using: .utf8)
let encrypted = try clear.encrypted(with: publicKey, padding: .OAEP)
I want to encrypt data in swift3 without touching server code.
As far as I know, Apple's security framework, it does not support OAEP padding with a SHA256 hash digest, but it can be done through the OpenSSL library.
https://github.com/x2on/OpenSSL-for-iPhone
Here is one piece of Chinese utf-8 text which is encoded by PHP on the server-side, but when I decode it with iOS, it returns null.
I also tried this online tool where text can be decoded well.
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:content options:0];
content = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding];
5aW96ZuF77yM5ZKx5p2l5LiA5L+X55qE77yM5pS56Ieq5Lic5Y2X6KW/5YyX6aOO44CCCuS4juS9oOebuOmAou+8jOWFqOaYr+acuue8mOW3p+WQiOOAguWPr+Wtpui1t+adpeWNtOW/g+aGlOaCtOOAggrmgLvmmK/ovpPkuoborqnlho3ljrvlrabvvIzlrabkuobo
Here is the test code for debug this issue with xcode:
NSString * = #"5aW96ZuF77yM5ZKx5p2l5LiA5L+X55qE77yM5pS56Ieq5Lic5Y2X6KW/5YyX6aOO44CCCuS4juS9oOebuOmAou+8jOWFqOaYr+acuue8mOW3p+WQiOOAguWPr+Wtpui1t+adpeWNtOW/g+aGlOaCtOOAggrmgLvmmK/ovpPkuoborqnlho3ljrvlrabvvIzlrabkuobo";
//
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString: options:0];
NSString *content = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding] ;
NSLog(content);
Your revised question features a base64 string of:
5aW96ZuF77yM5ZKx5p2l5LiA5L+X55qE77yM5pS56Ieq5Lic5Y2X6KW/5YyX6aOO44CCCuS4juS9oOebuOmAou+8jOWFqOaYr+acuue8mOW3p+WQiOOAguWPr+Wtpui1t+adpeWNtOW/g+aGlOaCtOOAggrmgLvmmK/ovpPkuoborqnlho3ljrvlrabvvIzlrabkuobo
This string has a length that is a multiple of four bytes, so the lack of the =/== terminator at the end is not the problem. And, in fact, initWithBase64EncodedString decodes it successfully:
e5a5bde9 9b85efbc 8ce592b1 e69da5e4 b880e4bf 97e79a84 efbc8ce6 94b9e887
aae4b89c e58d97e8 a5bfe58c 97e9a38e e380820a e4b88ee4 bda0e79b b8e980a2
efbc8ce5 85a8e698 afe69cba e7bc98e5 b7a7e590 88e38082 e58fafe5 ada6e8b5
b7e69da5 e58db4e5 bf83e686 94e682b4 e380820a e680bbe6 98afe8be 93e4ba86
e8aea9e5 868de58e bbe5ada6 efbc8ce5 ada6e4ba 86e8
The issue here is that this appears to not be a valid UTF8 string. In fact, when I run it through the http://base64decode.net site you referenced in your original question, it is also unable to convert it to a UTF8 string (I notice that your screen snapshots are using a different converter web site). When I ran it through another converter, it converted what it could, but then complained about the character following 学了 (which is, coincidentally, the character at which your base64 converter web site stopped, too).
By the way, the UTF8 representation of 了 is e4 ba 86. And you'll see that near the end of the hex representation of your base 64 string, followed by one more byte, e8. The thing is, e8, by itself, is not a valid UTF8 character. It almost looks looks like you took a base64 encoded string and just grabbed the first 200 bytes, disregarding whether that resulted in cutting the UTF8 character off in the middle or not.
The original question featured a base64 string of:
5aW96ZuF77yM5ZKx5p2l5LiA5L+X55qE77yM5pS56Ieq5Lic5Y2X6KW/5YyX6aOO44CCCuS4juS9oOebuOmAou+8jOWFqOaYr+acuue8mOW3p+WQiOOAguWPr+Wtpui1t+adpeWNtOW/g+aGlOaCtOOAggrmgLvmmK/
That is not valid base64. It should be a multiple of four bytes in length, but that is only 163 characters, which is missing a character. Either your server isn't properly terminating the base64 string, or it got cut off for some reason.
For example, if I add a = to get it up to 164 characters, I get a valid base64 string:
5aW96ZuF77yM5ZKx5p2l5LiA5L+X55qE77yM5pS56Ieq5Lic5Y2X6KW/5YyX6aOO44CCCuS4juS9oOebuOmAou+8jOWFqOaYr+acuue8mOW3p+WQiOOAguWPr+Wtpui1t+adpeWNtOW/g+aGlOaCtOOAggrmgLvmmK/=
Adding the = would be the right solution if the server simply neglected to terminate the base64 string properly. Anyway, that can be base64-decoded to:
好雅,咱来一俗的,改自东南西北风。
与你相逢,全是机缘巧合。可学起来却心憔悴。
总是
Is that what you were expecting?
Perhaps you should take a look at your base64 routine on your server? Or if it's getting truncated, look at how you are receiving it and compare the server's original base64 string length to what you have here.
For information about adding = or == to the end of a base 64 encoded string, see the base64 wikipedia page.
#Rob is right.
Check this Check NSData won't accept valid base64 encoded string
But in case if your server is not returning valid JSON with "=" or "==" then you need to use external methods to perform base64decode. Those methods can decode even if base64string does not have "=" symbol at the end.
Dwolla permits an app to solicit and store a user's PIN as a form of pre-authorization, but requires that it be encrypted. From the TOS:
PIN(s) must be encrypted in transit and at rest (this includes any and
all backup mediums) using FIPS 140-2 standards (at a minimum)
Normally, I'd use Bcrypt to encrypt (actually, make a secure hash. Neil Slater, thanks for the correction) something (using bcrypt-ruby gem), such as a password. But if I encrypt with Bcrypt, then I'd have to transmit the hash, and of course that won't match what Dwolla is expecting and the PIN will be rejected.
How do you encrypt the PIN and unencrypt it for secure transmittal?
UPDATE:
One of the answers in the question that Andrew links to below referenced OpenSSL:Cipher, and using that I can encrypt the PIN with the below code. But remaining questions then are:
How should I store the key, iv (initialization vector), and cipher? Is it secure to save as environment variables, or would it be better to put in a database table in a secure hash?
Does the below code make sense as a way to encrypt the PIN?
Since I don't have a public key from Dwolla, what's the best way to transmit it?
pin = "1111" # this is what needs to be encrypted
#encryption:
cipher = OpenSSL::Cipher.new('AES-128-CBC') #=> #<OpenSSL::Cipher:0x00000100ef09d8>
cipher.encrypt
key = cipher.random_key #=> odd characters...
iv = cipher.random_iv #=> odd characters...
encrypted = cipher.update(pin) + cipher.final #=> odd characters...
#dcryption:
decipher = OpenSSL::Cipher::AES.new(128, :CBC)
decipher.decrypt
decipher.key = key
decipher.iv = iv
plain = decipher.update(encrypted) + decipher.final
puts plain == pin #=> true
So this is what I've found out. In Rails, generate the key just once and store as an environment variable (and when you deploy encrypt it). Generate a new iv (initialization vector) for each pin. Store the iv and the encrypted pin in the database.
You may want to convert the encrypted PIN and the IV to UTF8 in order to successfully save without changing how you set up your database. (Be default, they'll be generated as ASCII 8-bit).
Here is one way to do it inside your User model, but you may want to refactor since these are large methods:
def dwolla_pin # => this is to decrypt the PIN in order to use it
unless encrypted_dwolla_pin.nil?
decipher = OpenSSL::Cipher::AES.new(128, :CBC)
decipher.decrypt
decipher.key = ENV["ENCRYPT_KEY"]
# Convert IV from UTF8 (as stored) back to ASCII-8bit (for OpenSSL)
utf8_iv = self.iv_for_pin
decipher.iv = Base64.decode64(utf8_iv.encode('ascii-8bit'))
# Convert PIN from UTF8 (as stored) back to ASCII-8bit (for OpenSSL)
utf8_pin = self.encrypted_dwolla_pin
ascii_pin = Base64.decode64(utf8_pin.encode('ascii-8bit'))
dwolla_pin ||= decipher.update(ascii_pin) + decipher.final
end
end
def dwolla_pin=(new_pin) # => this is to encrypt the PIN in order to store it
return false unless valid_pin?(new_pin)
cipher = OpenSSL::Cipher.new('AES-128-CBC')
cipher.encrypt
cipher.key = ENV["ENCRYPT_KEY"]
# Create IV and convert to UTF-8 for storage in database
iv = cipher.random_iv
utf8_iv = Base64.encode64(iv).encode('utf-8')
self.update_attribute(:iv_for_pin, utf8_iv)
# Encrypt PIN and convert to UTF-8 for storage in database
encrypted_pin = cipher.update(new_pin) + cipher.final
utf8_pin = Base64.encode64(encrypted_pin).encode('utf-8')
self.update_attribute(:encrypted_dwolla_pin, utf8_pin)
end
def valid_pin?(pin) # => Here I'm just checking to make sure the PIN is basically in the right format
pin.match(/^\d{4}/) && pin.length == 4
end
"Secure transit" means SSL for usage and SSH for deployment. If deploying to Heroku then already using SSH, but for SSL you will need to buy from your DNS host wildcard cert and the ssl endpoint on Heroku.
Does anyone have anything to add to this?
I'd use public/private key encryption in a case like this. Not an expert on Ruby, but this link might help:
Ruby: file encryption/decryption with private/public keys
If your pin is being sent externally then you'd need the end-users public key to encrypt. If this isn't possible then you could use a mixture of asynmmetric (public/private) and symmetric algorithms - basically what SSH does.
http://en.wikipedia.org/wiki/Secure_Shell