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).
Related
I' creating solr document via solarium plugin in php.
But the all document is stored text_general data type except id field. text_general is the default datatype in solr system.
My doubt is why id field is only to stored string type as default.
And If any possible to add document with string type using solarium plugin.
My code part is here,
public function updateQuery() {
$update = $this->client2->createUpdate();
// create a new document for the data
$doc1 = $update->createDocument();
// $doc1->id = 123;
$doc1->name = 'value123';
$doc1->price = 364;
// and a second one
$doc2 = $update->createDocument();
// $doc2->id = 124;
$doc2->name = 'value124';
$doc2->price = 340;
// add the documents and a commit command to the update query
$update->addDocuments(array($doc1, $doc2));
$update->addCommit();
// this executes the query and returns the result
$result = $this->client2->update($update);
echo '<b>Update query executed</b><br/>';
echo 'Query status: ' . $result->getStatus(). '<br/>';
echo 'Query time: ' . $result->getQueryTime();
}
The result document for the above code is here,
{
"responseHeader":{
"status":0,
"QTime":2,
"params":{
"q":"*:*",
"_":"1562736411330"}},
"response":{"numFound":2,"start":0,"docs":[
{
"name":["value123"],
"price":[364],
"id":"873dfec0-4f9b-4d16-9579-a4d5be8fee85",
"_version_":1638647891775979520},
{
"name":["value124"],
"price":[340],
"id":"7228e92d-5ee6-4a09-bf12-78e24bdfa52a",
"_version_":1638647892102086656}]
}}
This depends on the field type defined in the schema for your Solr installation. It does not have anything to do with how you're sending data through Solarium.
In the schemaless mode, the id field is always set as a string, since a unique field can't be tokenized (well, it can, but it'll give weird, non-obvious errors).
In your case i'd suggest defining the price field as an integer/long field (if it's integers all the way) and the name field as a string field. Be aware that string fields only generate hits on exact matches, so in your case you'd have to search for value124 with exact casing to get a hit.
You can also adjust the multiValued property of the field when you define the fields explicitly. That way you get only the string back in the JSON structure instead of an array containing the string.
I need to provide users with a unique link that contains their USER ID (please do not suggest usage of UUID etc in this case)
It's not that important, but I still rather make sure it's very difficult to extract the user id or guess the next one etc! (even if it's achieved with security by obscurity...)
I came up with this solution:
// #var $id int|string
function obfuscate_number($id, bool $reverse=FALSE)
{
$changing = (int)substr($id, -1);
$multiplier = '45' . $changing;
$base = 25;
// Obfuscate Number
if($reverse === FALSE)
{
$new = bcmul("$id", "$multiplier", 0);
$convert = bcadd("$new", "$changing", 0);
$obf = base_convert($convert, 10, $base) . $changing;
return $obf;
}
// Reverse to Number
else
{
$deobf = base_convert(substr($id, 0, -1), $base, 10);
$convert = bcsub("$deobf", "$changing", 0);
// Simple Validation
if($convert % $multiplier !== 0) return FALSE;
$number = (int)bcdiv("$convert", "$multiplier", 0);
return $number;
}
}
// For example number 123456 => 5dnpfi6
// After reversing 5dnpfi6 => 123456
// For example number 563 => g81h3
// After reversing g81h3 => 563
If it is possible please help me improve it.
Also I think the chance of collision is 0 here, am I correct?
Possible Solutions
Slow
Hashing, hashing will allow you to send the hash out and without an extremely moderate amount of computations will not be put reversed into the user id. The server would hash, send string to client, client visits webpage, you lookup in a database of some sort matching hash with user id.
Fast (Recommended)
Using AES encryption will allow you to encrypt data that is generally guaranteed to be unbreakable if you follow AES guidelines. So an approach would be to encrypt data with AES and convert to base 64. Send the base64 to the user and when the user clicks the link, you simply just need to convert base 64 to binary, and decrypt. I would say this is considerably faster than the hashing approach.
How can I do hashing url paramaters in Laravel?
I know the Hash::make method, but that's a method for passwords (those hashes are not very url-friendly).
Does Laravel have a beter alternative, so I can hash parameters like http://url?key=2jd8dka72
you can use Laravel Encrypt function for that .
put
use Illuminate\Support\Facades\Crypt;
in header section and than use Crypt::encrypt($param) to encrypt param and Crypt::decrypt($param) to decrypt it.
Just add base64 encoding to make it more friendly looking.
use Hash;
...
$id = 15;
$key = base64_encode(Hash::make($id));
echo "http://someurl?send_mail_to_user=$id&key=$key";
When you check it:
use Hash;
...
$keyDecoded = base64_decode($request->key);
if(Hash::check($request->id, $keyDecoded)) {
// checked
}
Another way is to use some complicated function like a large number at another base. But it is not secured (just security through obscurity):
echo base_convert($id * 250 + 5675675, 10, 33); // converts 15 to 4q18q
echo ((base_convert('4q18q', 33, 10) - 5675675) / 250); // converts back, but this one is not being used
// checking:
if(base_convert($request->id * 250 + 5675675, 10, 33) === $request->key) {
// checked
}
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/
this is my first post and I'm very new with mysql and php.
I'm currently doing AES encryption for passwords.
I'm using this encryption: http://www.phpclasses.org/package/4238-PHP-Encrypt-and-decrypt-data-with-AES-in-pure-PHP.html since we don't have SSL security and must protect our server side as well.
It gives me an encrypted string like this : '�<�rB�5�]��MJ' and mysql fails at inserting the string even though I put the column type in unicode-general.
Can you help this poor damsel?
Thank you for your time.
<?php
$input = '123456';
function Encrypt($toEncrypt)
{
$Cipher = new AESCipher(AES::AES256);
$password = 'superKeyHere';
$cryptext = $Cipher->encrypt($toEncrypt, $password);
return CleanUpString($cryptext);
}
function Decrypt($toDecrypt)
{
$Cipher = new AESCipher(AES::AES256);
$password = 'superKeyHere';
$output = $Cipher->decrypt($toDecrypt, $password);
return CleanUpString($output);
}
function CleanUpString($inp)
{
return str_replace(array("�", "ۓ"), array("=^_^=", "=^.^="), $inp);
}
$cryptext=Encrypt($input) ;
//Encrypted
print 'cryptext: '.$cryptext.'<br />';
$oSql = new sql(0);
$cryptext=mysql_real_escape_string($cryptext);
$oSql->query("update userTab set pass='$cryptext' where id=1");
$oSql = new sql(0);
$oSql->query("select pass from userTab where id=1");
$rows = $oSql->get_table_hash();
$cryptext="";
if (sizeof($rows) >0){
$cryptext= $rows[0]["pass"];
}
$cryptext=Decrypt($cryptext);
//Decrypted
print 'message: '.$cryptext.'<br />';
?>
To store data that you encrypt, it is probably necessary to use some kind of BLOB field like VARBINARY. Otherwise, MySQL will try to validate the data, which almost certainly will not be valid Unicode data. Another possibility would be to convert the encrypted data to Base64 encoding. That data could then be stored in a Unicode (or even ANSI) field.
In your particular case I recommend BASE16-encoding the encrypted data (BASE-16 is when each character is replaced by it's hex code, with 20 being for " "/space, 41 being for "A" etc). This way you get alphanumeric string which can be safely inserted into the DB.
And even better approach is to not keep passwords in the database, instead keeping the salt (some unique value) and hash of (password+salt). This is much more secure from many aspects.
You could try the format Windows-1252. Maybe that works.