I have made an PHP rest api. I want to connect to the API via an IOS and Android app. But I don't know how to secure everything.
I register the device at my database when the app fires for the first time
Table devices:
id random enabled
1 12345 1
Every device has an id and an random. The random value is unique in that table. The actual device receives the id and random value.
What I have right now:
I validate each request at the php side:
private function validateUrl(){
$url = $_SERVER['REQUEST_URI'];
$signature = isset($_GET['signature']) ? $_GET["signature"] : null;
$url = str_replace('&signature=' . $signature, '',$url);
$url = "" . $url;
$correctSignature = md5($url . "TNynVX9k2HqYSXnd");
if($signature != $correctSignature){
echo die(json_encode([array('status' => "not valid")]));
}
}
The request at (in this case) the IOS side:
private func random () -> Int {
var result = "";
for _ in 1...3 {
let randomNumber = arc4random_uniform(99)
result += String(randomNumber);
}
return Int(result)!;
}
private func md5(string string: String) -> String {
var digest = [UInt8](count: Int(CC_MD5_DIGEST_LENGTH), repeatedValue: 0)
if let data = string.dataUsingEncoding(NSUTF8StringEncoding) {
CC_MD5(data.bytes, CC_LONG(data.length), &digest)
}
var digestHex = ""
for index in 0..<Int(CC_MD5_DIGEST_LENGTH) {
digestHex += String(format: "%02x", digest[index])
}
return digestHex
}
func createUrl(url : String) -> String {
var newUrl = url;
newUrl += "?&random=\(random())";
let secret = "TNynVX9k2HqYSXnd"
let signature = md5(string: newUrl + secret)
newUrl += "&signature=" + signature;
return newUrl;
}
This works great but as you see I have an static API key. Something where I have my concerns about. So I thought maybe I can create an API key based on the id and random from my database. Is that more secure?
Something like:
func createUrl(url : String) -> String {
var newUrl = url;
let signature = md5(string: [device id here] + [device random here])
newUrl += "&signature=" + signature;
newUrl += "&deviceId=" + [device id here];
return newUrl;
}
And at my PHP side I can get the deviceId property from the url. Compare it to the database, retrieve the id and random value. MD5 them. Compare that to the signature. And when there is an match it's ok. Otherwise not. Is that an solid implementation?
Or in simple terms. Can I replace the api key with the combination of id + random?
Looks like you are using the same secret client side and server side, which can be an issue since anyone who has access to the *.apk or *.ipa could disassemble it and find the token, which is pretty easy with open source tools (https://github.com/iBotPeaches/Apktool). Especially on Android where the APK is pretty much a jar with some other assets. Are you generating the random token client side? Since it looks to be an int, that's only 32 bits of entropy, not enough to be secure.
Usually, the token is created server side rather than client side. There are many different ways of doing this. One is a JSON Web Token (JWT) which basically encodes data like an id or expires data into a token which is signed with a private key. Only the server knows the private key, and thus it's the trusted source for creating them, but others can have access to a public key which can be used to verify the token.
If you don't want to deal with JWT's and signing, a second option is just opaque tokens that are created server side. The key is a large amount of entropy like a large UUID.
There is a lot of information on asymmetric algorithms that generate a mathematically linked public/private key pair.
You can read more here: https://www.moesif.com/blog/technical/restful-apis/Authorization-on-RESTful-APIs/
Related
I've been trying to get this for hours now, and I can't find what's wrong. I'm using a php RESTful API that I made to encrypt data using asymmetric encryption.
First, I save my user's public key in the server by exporting it in android:
fun exportPublicKey() : String {
val publicKey = getPublicKey()
return android.util.Base64.encodeToString(
publicKey!!.encoded,
android.util.Base64.NO_WRAP
)
}
This allows me in the PHP server to do that:
$public_key_core = $_POST["public_key"];
$public_key = "-----BEGIN PUBLIC KEY-----\n" . $public_key_core . "\n-----END PUBLIC KEY-----";
I am unsure that's the right way but openssl seems to be "ok" with that key ?
I then tested my keystore in local using both keys, and it works just fine using this code:
Encrypt:
fun encryptAsymmetricData(data: String, usePrivateKey : Boolean = true): ByteArray {
val cipher : Cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
val encryptedBytes: ByteArray
if (usePrivateKey){
cipher.init(Cipher.ENCRYPT_MODE, getPrivateKey())
encryptedBytes = cipher.doFinal(data.toByteArray(Charsets.UTF_8))
} else {
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey())
encryptedBytes= cipher.doFinal(data.toByteArray(Charsets.UTF_8))
}
return encryptedBytes
}
Decrypt:
fun decryptAsymmetricData(data: ByteArray): String{
val cipher : Cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey())
return cipher.doFinal(data).toString(Charsets.UTF_8)
}
Using this works because I do ".toByteArray(Charsets.UTF_8)" on the encryptData result.
Now here's the problem, I use base64 encoding and do the following to encrypt in PHP:
openssl_public_encrypt($token->token, $encrypted_token, $user->public_key);
openssl_public_encrypt($user->id, $encrypted_id, $user->public_key);
[...]
'encrypted_user_id' => base64_encode($encrypted_id),
'encrypted_token' => base64_encode($encrypted_token)
But when I try to decrypt this in Android I'm getting an exception "javax.crypto.IllegalBlockSizeException" caused by this code:
val tokenBA = String(getDecoder().decode(this.encryptedToken), Charsets.UTF_8).toByteArray(Charsets.UTF_8)
val userIDBA = String(getDecoder().decode(this.encryptedUserId), Charsets.UTF_8).toByteArray(Charsets.UTF_8)
val token = App.encryptionController.decryptAsymmetricData(tokenBA)
val userID = App.encryptionController.decryptAsymmetricData(userIDBA)
(The logic being, I use base64 to send back my data in PHP, so I convert it to UTF8 in Android, then get the associated ByteArray to decrypt it ?)
I know that the encryption works in "local" but it doesn't when using both PHP and KeyStore, so I guess the problem is coming either from the PHP encryption, or from the way I try to decrypt it in android, but I can't seem to find what wrong, could you guys help me there please ?
Thank you by advance!
Ok, after searching and making sure the issue wasn't the public key stored in the PHP server, I found the answer. It was caused by the way to convert the "base64" string in an actual ByteArray in the App. This worked:
val token = App.encryptionController.decryptAsymmetricData(getDecoder().decode(encryptedToken))
val userID = App.encryptionController.decryptAsymmetricData(getDecoder().decode(encryptedUserId))
This is only working because I do the "base64_encode" in the server, for some (bad) reason I thought it was needed to go back to UTF8 to get the ByteArray in the app.
Assume a MySQL database that has two fields: name and email. Also, assume that both fields need to be securely encrypted at rest due to privacy concerns. And assume that a third field contains a GUID that is the key used to encrypt the name/amail fields. (The third field value is used along with another value as the encryption key to reduce the possibility of having a 'master' key for all encryption. )
The application uses PHP (Version 7.4+) and MySQL.
How would you search the database for a plaintext value (name or email) when the data is encrypted at rest?
Added
Data will be encrypted with openssl_encrypt(). (And changed the above to MySQL.)
How would you search the database for a plaintext value (name or email) when the data is encrypted at rest?
You can't do this in general with arbitrarily encrypted data, but a library like CipherSweet make this really easy.
The documentation should help you get the library installed.
To use it, you'll want to first setup a KeyProvider, then create an EncryptedRow object like so:
<?php
use ParagonIE\CipherSweet\BlindIndex;
use ParagonIE\CipherSweet\CipherSweet;
use ParagonIE\CipherSweet\CompoundIndex;
use ParagonIE\CipherSweet\EncryptedRow;
use ParagonIE\CipherSweet\Backend\FIPSCrypto;
use ParagonIE\CipherSweet\KeyProvider\StringProvider;
// Example key provider
$keyProvider = new StringProvider('4e1c44f87b4cdf21808762970b356891db180a9dd9850e7baf2a79ff3ab8a2fc');
// Setup the engine with a key provider. FIPSCrypto uses OpenSSL.
$engine = new CipherSweet($provider, new FIPSCrypto());
// EncryptedRow object setup:
$rowProcessor = (new EncryptedRow($engine, 'your_table_name_here'))
->addTextField('name')
->addTextField('email');
$rowProcessor->addBlindIndex(
'name',
new BlindIndex(
'my_table__name_literal',
16 /* See below */
)
);
$rowProcessor->addBlindIndex(
'email',
new BlindIndex(
'my_table__email_literal',
16 /* See below */
)
);
In this example, I added two blind indexes with a size of 16 bits each. This is a fudged number; you'll want to look at blind index planning for which values to use for guidance here.
Now you need to update your code when you read/write data into your MySQL database, using $rowProcessor to transparently encrypt/decrypt your data on-the-fly (in diff format):
/** #var array<string, mixed> $inputValues */
- $db->insert('your_table_name_here', $inputValues);
+ [$inputValuesSomeEncrypted, $indices] = $rowProcessor->prepareForStorage($inputValues);
+ // If you wish to store the blind indexes in the same table:
+ $inputValuesSomeEncrypted['name_idx'] = $indices['my_table__name_literal'];
+ $inputValuesSomeEncrypted['email_idx'] = $indices['my_table__email_literal'];
+ $db->insert('your_table_name_here', $inputValuesSomeEncrypted);
Next, you're going to need to tweak for your lookup logic.
- $rows = $db->lookup("name = ? OR email = ?", [$row['name'], $row['email']]);
+ $index1 = $rowProcessor->getBlindIndex('my_table__name_literal', $row);
+ $index2 = $rowProcessor->getBlindIndex('my_table__email_literal', $row);
+ $rowsWithCiphertext = $db->lookup("name_idx = ? OR email_idx = ?", [$index1, $index2]);
+
+ /* We need to post-process to eliminate coincidences in the blind index */
+ $rows = [];
+ foreach ($rowsWithCiphertext as $rowC) {
+ $decrypted = $rowProcessor->decryptRow($rowC);
+ if (!hash_equals($decrypted['name'], $row['name']) && !hash_equals($decrypted['email'], $row['email'])) {
+ continue;
+ }
+ $rows[] = $decrypted;
+ }
The exact code changes necessary will look different based on whatever your original code looks like. I'm just trying to demonstrate the structure here.
This will allow you to store encrypted data in your database yet still use the provided name or email parameters in the WHERE clause of your SQL queries (with a layer of abstraction).
I have a project I'm working on that uses an API for it request, but in order to preform them I need to generate the token first.
Before the API was update everything was working, after the update I don't know how to adjust my code to make it work again.
This was the code that worked before the update (Android | Kotlin):
fun hmacHash(str: String, secret: String): String {
val sha256HMAC = Mac.getInstance("HmacSHA256")
val secretKey = SecretKeySpec(secret.toByteArray(), "HmacSHA256")
sha256HMAC.init(secretKey)
return convertToHex(sha256HMAC.doFinal(str.toByteArray()))
}
fun convertToHex(data: ByteArray): String {
val buf = StringBuilder()
for (b in data) {
var halfbyte = (b.toInt() shr 4) and (0x0F.toByte()).toInt()
var two_halfs = 0
do {
buf.append(if (halfbyte in 0..9) ('0'.toInt() + halfbyte).toChar() else ('a'.toInt() + (halfbyte - 10)).toChar())
halfbyte = (b and 0x0F).toInt()
} while (two_halfs++ < 1)
}
return buf.toString()
}
Which was equivalent to this PHP code:
hash_hmac('sha256', $string, $privateKey);
But now after the update the php code looks like this:
hash_hmac('sha256', $string, hex2bin($privateKey));
And I don't know how to adjust my code to make it work with this new change.
From what I can deduce, the PHP code made that change because $privateKey went from being plain text to being hex-encoded. So hex2bin was needed to change it back to plain text (hex2bin changes hex-encoded text to plain text; a confusingly named function if you ask me).
Since your secret is plain text, you don't need to change anything to match. But there are other ways to improve your code. For example, converting a byte array to a hex-encoded string is much easier than that.
fun hmacHash(str: String, secret: String): String {
val sha256HMAC = Mac.getInstance("HmacSHA256")
val bytes = secret.toByteArray()
val secretKey = SecretKeySpec(bytes, "HmacSHA256")
sha256HMAC.init(secretKey)
return convertToHex(sha256HMAC.doFinal(str.toByteArray()))
}
fun convertToHex(data: ByteArray): String =
data.joinToString("") { "%02x".format(it) }
I'm trying to obtain the same encryption results (using AES 256) between PHP/mySQL and Swift.
Here is the text i'm trying to encrypt : {"email":"aze#aze.com","password":"aze"}
Here is the key : toto
I'm using the SHA1 hash of the key to encrypt : 0B9C2625DC21EF05F6AD4DDF47C5F203837AA32C
Here is my PHP code (the result is what I expect, I use a local db to perform the encryption)
$data = array();
$data['email'] = 'aze#aze.com';
$data['password'] = 'aze';
$json = json_encode($data);
$request = $db->prepare("select AES_ENCRYPT('$json', SHA1('toto')) as data from dual");
$request->execute();
$request->setFetchMode(PDO::FETCH_ASSOC);
$encodedResult = $request->fetchAll();
$encodedResult = $encodedResult[0]['data'];
$base64Result = base64_encode($encodedResult));
Encoded result is ¤]¼–áú£?îfÞð"2Á«¯ä%s7Ûš>½qé}‘(J µƒ–"³}vÃë
Base64 result is pF28A5bh+qOdP+5mHN7wIjLBBKutr+Qlczfbmj69cel9kRYoShcgtYOWIrN9dsPr
How can I obtain the same result in Swift or Objective-C ? I tried different libraries such as CryptoSwift / CocoaSecurity, but the result is always different.
It seems that MySQL uses 0 padding, which I can't get to work on iOS.
Edit : To be clear I need to implement 0 padding on iOS side to obtain the same result as MySQL, and not PKCS7 on PHP/MySQL side.
With CryptoSwift you can easily apply custom padding
public struct ZeroPadding: Padding {
func add(data: [UInt8], blockSize:Int) -> [UInt8] {
// Padding logic here
}
func remove(data: [UInt8], blockSize:Int?) -> [UInt8] {
// Padding logic here
}
}
and pass it as parameter to encrypt()
let encrypted = AES(key: key, iv: iv, blockMode: .CBC)?.encrypt(message, padding: ZeroPadding())
I have a rest service that my website calls it and I want to secure it from calling outside of my website as much as possible.
I want to create a token mixed with timestamp, so the user can only call the service in 10 minutes (for example) with the token that generated in the server.
Let me explain it with some pseudo codes:
1) Server: The token is generated in the server using a private key and timestamp:
// The token is valid only for 10 minutes after 'time'
$token = encrypt($pKey, timestamp); // Server Time
2) Client: We put the token in the javascript variable and use it in our request with the timestamp of the client:
var token = '<?= $token ?>';
var params = {
token : token,
time : timestamp, // Client Time
data : mydata
}
3) Server: If the time parameter mixed with token is not equal to 10 minutes token, the request is invalid:
// I'm Stuck Here
$something = decrypt($pKey, $_POST['token'], $_POST['time']);
if ($something != $tenMinutes) { // Invalid Request }
The Question:
1) Is this senario O.K? If Yes, What is the exact solution? If No, So What is the solution?
2) There is another senario to secure the requests that I've seen in the AWS: http://docs.aws.amazon.com/AmazonS3/latest/dev/S3_Authentication2.html
How can I implement this in PHP?
After you've got token from the client you need to check two things: validity of the token and its timestamp.
There are two scenarios:
Make timestamp part of the token:
function getToken($timestamp) {
return $timestamp . encrypt(getPKey(), $timestamp);
}
$token = genToken(time());
And then validate it:
$token = $_POST['token'];
function validate($token) {
$timestamp = substr($token, 0, 10);
return
(genToken($timestamp) == $token)
&& ($timestamp >= time() - 600);
}
Save generated token in database along with timestamp and after getting token from the client check corresponding timestamp.