I have Facebook and Google login in my application, I use my backend server to store data about the user, such as name and status.
I am sending the token along side with some info like user points, the server uses the token identifies the user and does his work just fine.
Before publishing the app i want to encrypt everything, I know I can use SSL however my provider charges A LOT of money for SSL support.
My idea was to genarate a RSA Keypair, save the private on a safe place, and have the public in the apk.
I can generate encrypt and decrypt using rsa within my app very easily, but I'm not an expert in php i tried a lot of things to decrypt stuff in server side but i can't figure it out how to do it.
I have one Keypair generated by android, i used,
getPublic().getEncoded()
getPrivate().getEncoded()
How can if use the private key in php to decrypt and encrypt data?
I know that this may not be the best way to do things but i think i won't have a problem, the target audience is really far from hackers.
Because you added the tag PHP, i am assuming that you have some kind of rest api running that you are calling from your android app. Now you don't need encrypt and decrypt in PHP. Those are handled by your web servers. As far as ssl goes have a look at let's encrypt which is opensource. Enforcing ssl alone on web server is pretty good security measure.
I think i achived what i was tring to do, login is 100% handle by facebook and google via https, i only use tokens to identity the user in my server and increment the score
1- Token and score is encrypted and sent to the server
2- Using the private key the server finds the token and i use https to make calls to Facebook or Google to retrieve the user id and increment the score
Note that all data stored in my server is 100% public, i don't store private information about anyone, i just want to protect the token, if someone gets the token and starts to make a lot of calls it may reach the facebook limit of 200 calls/hour per user, making my app inoperable.
I will upgrade to SSL in the future, when i start to earn revenue from the app
Android
String pubKeyPEM = "***";
public void something(){
String sendToServer = Base64.encodeToString(RSAEncrypt("test"),0);
}
public byte[] RSAEncrypt(final String request) throws Exception {
PublicKey publicKey = getPublicKey();
cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(plain.getBytes());
}
public PublicKey getPublicKey() throws Exception {
PublicKey publicKey;
byte[] decoded = Base64.decode(pubKeyPEM, Base64.DEFAULT);
KeyFactory kf = KeyFactory.getInstance("RSA");
publicKey = kf.generatePublic(new X509EncodedKeySpec(decoded));
return publicKey;
}
PHP
$privkey = '-----BEGIN RSA PRIVATE KEY-----';
function decrypt($data){
global $privkey;
if (openssl_private_decrypt(base64_decode($data), $decrypted, $privkey))
$data = $decrypted;
else
$data = '';
return $data;
}
The private key will be moved to a safer place, but this is working just as i wanted
my server is also checking if the token was generated by my app id, so if someone tries to use a diferent token, it will show a diferent app id.
Related
I am attempting to hash and sign user data on iOS (14.4), send that to my server, and have the server verify the hash and the signature with a previously uploaded public key (sent on keypair generation during user creation). It seems a number of people have run into issues with this, but all of the answers I've been able to find are very old, don't factor in using Apple's Secure Enclave, or revolve around signing and verifying on the same iOS device.
The general workflow is: User creates an account on iOS, and a random keypair is created on the device with the private key remaining in the Secure Enclave, while the public key is converted to ASN.1 format, PEM encoded and uploaded to the server. When the user later signs data, the data is JSONEncoded, hashed with sha512, and signed by their private key in the Secure Enclave. This is then packaged into a base64EncodedString payload, and sent to the server for verification. The server first verifies the hash using openssl_digest and then checks the signature using openssl_verify.
I have been unable to get the openssl_verify method to successfully verify the signature. I have also attempted using the phpseclib library (to get more insight into why the verification fails) without success. I understand phpseclib uses the openssl library if it is available, but even if this is disabled, phpseclib's internal verification fails because the resulting values after modulus do not match. Interestingly, phpseclib converts the public key to what looks like PKCS8 formatting with a large amount of padding.
It appears the public key is being parsed and loaded properly by openssl, as a proper reference is being created prior to verification. However, since the private key is opaque (residing in the Secure Enclave) I don't have a way to externally "check" how the signatures themselves are generated/encoded or if the same signature would be created outside of the iOS device. I'm wondering if I have an encoding error, or if external verification is possible with keys generated in the Secure Enclave.
iOS Public Key Upload method- I am using CryptoExportImportManager which converts the raw bytes to DER, adds the ASN.1 header, and adds the BEGIN and END key tags.
public func convertPublicKeyForExport() -> String?
{
let keyData = SecKeyCopyExternalRepresentation(publicKey!, nil)! as Data
let keyType = kSecAttrKeyTypeECSECPrimeRandom
let keySize = 256
let exportManager = CryptoExportImportManager()
let exportablePEMKey = exportManager.exportECPublicKeyToPEM(keyData, keyType: keyType as String,
keySize: keySize)
return exportablePEMKey
}
An example of what one of the public keys looks like after upload
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEf16tnH8YPjslaacdtdde4wRQs0PP
zj/nWgBC/JY5aeajHhbKAf75t6Umz6vFGBsdgM/AFMkeB4n2Qi96ePNjFg==
-----END PUBLIC KEY-----
let encoder = JSONEncoder()
guard let payloadJson = try? encoder.encode(["user_id": "\(user!.userID)", "random_id": randomID])
else
{
onCompletion(nil, NSError())
print("Failed creating data")
return
}
let hash = SHA512.hash(data: payloadJson)
guard let signature = signData(payload: payloadJson, key: (user?.userKey.privateKey)!) else
{
print("Could not sign data payload")
onCompletion(nil, NSError())
return
}
let params = Payload(
payload_hash: hash.hexString,
payload_json: payloadJson,
signatures: ["user": [
"signature": signature.base64EncodedString(),
"type": "ecdsa-sha512"
]]
)
let encoding = try? encoder.encode(params).base64EncodedString()
The sign data function is pretty close to Apple's documentation code, but I'm including it for reference
private func signData(payload: Data, key: SecKey) -> Data?
{
var error: Unmanaged<CFError>?
guard let signature = SecKeyCreateSignature(key,
SecKeyAlgorithm.ecdsaSignatureMessageX962SHA512,
payload as CFData, &error)
else
{
print("Signing payload failed with \(error)")
return nil
}
print("Created signature as \(signature)")
return signature as Data
}
I actually stumbled upon the solution while doing additional research and experimentation while writing this question. The problem of course had nothing to do with the keys or algorithms, and everything to do with the way Apple hashes data objects.
I had discovered a similar problem when trying to determine why my hashes were not matching on the server-side vs the ones created on the iOS device. The user JSONEncoded data is hashed and signed as a base64Encoded data object, but unknown to me (and not in any documentation I could discover) iOS decodes the Data object and hashes the raw object, and re-encodes it (since this is opaque code it's possible this is not precisely accurate, but the result is the same). Therefore when checking the hash on the user data, I had to first base64decode the object, and then perform the hash. I had assumed that Apple would sign the encoded object as is (in order to not contaminate its integrity), but in fact, when Apple creates the digest before signing, it hashes the decoded raw object and creates a signature on the raw object.
Therefore the solution was to again base64decode the object before sending it to the openssl_verify function.
Checking the hash on the server
public function is_hash_valid($payload) {
$server_payload_hash = openssl_digest(base64_decode($payload["payload_json"]), "SHA512");
$client_payload_hash = $payload["payload_hash"];
if ($client_payload_hash != $server_payload_hash) {
return false;
}
return true;
}
Verifying the signature on the server
function is_signature_valid($data, $signature, $public_key) {
$public_key = openssl_get_publickey($public_key);
$ok = openssl_verify(base64_decode($data), base64_decode($signature), $public_key, "SHA512");
if ($ok === 1) {
return true;
} else {
return false;
}
}
After discovering this, and verifying that openssl_verify and phpseclib's verify function worked correctly, I almost considered deleting the question entirely but realized that if I had discovered a question similar to this in my research, it might have saved me a good deal of time. Hopefully to anyone else that has a similar issue, this will prove helpful.
Case 1:Encrypted Data from client to Server
Need to create a public/private key in server.Public key provided to client.The client encrypts it with public key.The server decrypts it using the private key that was generated.
Case2: But what about the case when the data from server is sent to the
client side.??
That data should also be encrypted.Does the same public/private that was created for case 1 work or new key should be generated?can anybody please explain case2 for my requirements.
Thanks in advance.
As outlined in this white paper on secure data encryption and its supporting material:
Use HTTPS.
For a second layer of authenticated encryption, use libsodium (a modern, cross-platfrom cryptography library) with pinned public keys.
PHP Example
Key Generation
$bob_box_kp = \Sodium\crypto_box_keypair();
$bob_box_secretkey = \Sodium\crypto_box_secretkey($bob_box_kp);
$bob_box_publickey = \Sodium\crypto_box_publickey($bob_box_kp);
Encryption
$anonymous_message_to_bob = \Sodium\crypto_box_seal(
$message,
$bob_box_publickey
);
Decryption
$decrypted_message = \Sodium\crypto_box_seal_open(
$anonymous_message_to_bob,
$bob_box_kp
);
if ($decrypted_message === false) {
// You have the wrong keypair or the message was tampered with.
}
Android Example (using Libstodium)
Key Generation
byte[] secret_key = new byte[Box.SECRETKEYBYTES];
byte[] public_key = new byte[Box.PUBLICKEYBYTES];
Box.keypair(public_key, secret_key);
Encryption
Box.seal(
ciphertextByteArray, // Output goes here
plaintextByteArray, // Your message
public_key
);
Decryption
Box.sealOpen(
plaintextOutputByteArray, // Decrypted data goes here
ciphertextByteArray, // Encrypted message received over the wire
public_key,
secret_key
);
You can use public / private key encryption in any direction.
So your client can encrypt with public key, then the server can decrypt with private key.
If your server encrypts with private key, the client can decrypt with public key.
For a project I'm working on, I'm using the Amazon AWS SDK for PHP, and I needed to retrieve a password for a server environment in plain text format. However, the documentation for the ec2 method confirmed what we found: the method would only return an encrypted string. On the surface, this was good, because the AWS SDK for PHP uses an unencrypted HTTP POST request to send and receive data via cURL, invisibly to the user. So we don't our password data just flying around the web.
The problem was that there was nothing explaining how to decrypt the string. I had my private key as a PEM file, but there was no method or documentation for what to do with that string to make it usable. Several attempts yielded nothing, and I was beginning to think that I needed to rethink my strategy for the project I'm on, but then I found the code from the last version of the AWS SDK for PHP, and it revealed how to go about decrypting the string to produce a plain text form of the password.
The answer I found was that the getPasswordData method returns a string that is BOTH base64 encoded AND encrypted. You need to decode it with base64_decode() before you can successfully decrypt it with PHP's OpenSSL library. The following function takes care of both:
/**
* #param obj $ec2_client The EC2 PHP client, from the AWS SDK for PHP
* #param string $client_id The ID of the client whose password we're trying to get.
* #return mixed The unencrypted password for the client, or false on failure.
*/
function aws_get_ec2_password($ec2_client, $client_id){
// First, run getPasswordData to get the Password Data Object.
$pw_obj = $ec2_client->getPasswordData($client_id);
// Next, use the local get() method to isolate the password
$pw_b64 = $pw_obj->get("PasswordData");
// Decode the password string.
$pw_encrypted = base64_decode($pw_b64);
// Now, get your PEM key.
//
// You can also use a raw string of the PEM key instead of get_file_contents(),
// or adjust the function so that you can pass it as an argument.
//
// Technically, this step might not be necessary, as the documentation for
// openssl_private_decrypt() suggests that $key can just be the path, and it will
// create the key object internally.
$key = openssl_get_privatekey(file_get_contents("path/to/key.pem"));
// Create an empty string to hold the password.
$pw = "";
// Finally, decrypt the string and return (will return false if decryption fails).
if(openssl_private_decrypt($pw_encrypted, $pw, $key)){
return $pw;
}else{
return false;
}
}
I hope this helps someone else avoid the headaches it gave me!
I am a newbie to API development, but have successfully managed to implement CI REST Server by Phil Sturgeon and Chris Kacerguis by reading all articles I could find, but there is one answer that eludes me, from the following question: CodeIgniter REST API Library Ajax PUT throwing 403 Forbidden and How can I generate an API Key in My own Controller in Codeigniter.
I have added the "boguskey" to the database as suggested in the first question's accepted answer, but I am confused about security here. If I need to have a hard-coded API key to generate new keys, and someone can view the header to see this bogus API key, how do I secure my API then from someone who then use this API key to generate tons of API keys for us within my API? If I do not add the boguskey, then I get "Invalid API key" regardless of which function I call.
My apologies if this is a stupid question, but if someone has an example of how I can generate keys securely (or at least inform me if I am misinterpreting the situation) I will greatly appreciate it.
To ensure the max security you should encrypt all the sent data, then if the API could decrypt it correctly you should be fine, you can use RSA encryption, so if any one intercept the request he cant decrypt or clone it, But RSA is not designed to be used on long blocks of plain text, so you can use hybrid encryption. Namely, this involves using RSA to asymmetrically encrypt a symmetric key.
Randomly generate a symmetric encryption (say AES) key and encrypt the plain text message with it. Then, encrypt the symmetric key with RSA. Transmit both the symmetrically encrypted text as well as the asymmetrically encrypted symmetric key.
The API can then decrypt the RSA block, which will yield the symmetric key, allowing the symmetrically encrypted text to be decrypted.
To implement RSA on CodeIgniter you can use this class, call the file on your controller require_once("RSA.php");.
On the API consumer controller make an array which will contain the data and the the asymmetrically encrypted symmetric key
$request_data = array();
$request_data["username"] = "taghouti";
$request_data["project"] = "Secured_API";
$serialized_request_data = serialize($request_data);
$enc = new RSAEnc($serialized_request_data,'public_key');
$encrypted = $enc->result();
$request_data = array(
"data" => base64_encode($encrypted->result),
"key" => base64_encode($encrypted->key)
);
And on the API controller you should try to decrypt the symmetric key using your private key, if the decryption done successfully you should be fine
if ($_POST["key"]) {
$key = base64_decode($_POST["key"]);
$_POST["key"] = null;
if (isset($_POST["data"])) {
$data = base64_decode($_POST["data"]);
$dec = new RSADec($data, 'private_key', $key);
$decrypted = $dec->result();
if($decrypted->success !== true) die("Decryption failed");
$decrypted = #unserialize($decrypted->result);
$_POST = is_array($decrypted) ? $decrypted : array();
$this->_post_args = $_POST;
}
}
if($this->input->post('project') && $this->input->post('username')) {
//Enjoy
} else {
die('data parsing error');
}
I'm building a new project and I'm having some debate over how it needs to be developed. The big picture is to develop a consumable JavaScript widget that other internal developers can embed into their web applications. The trick is that the consumer needs to be able to tell me what AD user is currently logged into their page...and then I need to trust that the passed username is coming from the consumer and hasn't been tampered with via outside sources.
The overall solution needs to have a VERY simple set-up on the consuming side involving no compiled code changes. Also it needs to be functional across both ASP.net and PHP applications (hence my decision to go with JavaScript).
Overall, it's kind of like an Oauth solution...except the trust between domains can be intrinsic since I'll already know every user in the company trusts the host domain.
I started stubbing it out and got kind of stuck. My idea was that I would basically host a JavaScript file that the client host could embed in their page. During their page load cycle, they could init my JavaScript widget and pass it a plain text username (all I really need). Somehow I would establish an secure trust between the client host's web page, and my widget so that it would be impossible for a third-party to embed my widget into a false web page and send action commands under a user other than their own.
I hope this makes sense to someone.
I haven't really discovered an answer so to speak, but I've decided on a method:
So, I decided on a pattern where I write my JavaScript and HTML widget using the proposed jQuery UI Widget Factory. That allows the my consumer to implement the widget using simple syntax like:
<script src="widget.js"></script>
$('#someElement').myWidget({ encryptionUrl: handlerPath });
Now, you'll noticed that as part of my widget, I ask the consumer to pass a "handlerPath." The "handler" is simply an Microsoft MVC Controller which is in charge of getting the logged in user, and encrypting the call.
So the handler in my app looks something like this...
[Authorize]
public JsonpResult GetToken(string body, string title, string sender)
{
Packet token = new Packet();
try
{
// Get the widget host's public cert
string publicKey = "some.ssl.key.name.here";
// Get the consumer host's private cert
string privateKey = "this.consumers.ssl.key.name.here";
// Build a simple message object containing secure details
// Specifically, the Body will have action items (in JSON) from my widget
// The User will be generated from the consumer's backend, thus secure
Message message = new Message(){
Body = body,
Title = title,
User = System.Web.HttpContext.Current.User.Identity.Name,
EncryptionServerIP = Request.UserHostAddress,
Sender = new Uri(sender),
EncryptionTime = DateTime.Now
};
PacketEncryption encryption = new PacketEncryption();
// This class just wraps basic encryption and signing methods
token = encryption.EncryptAndSign(message, publicKey, privateKey);
token.Trust = "thisConsumerTrustName";
}
catch (Exception exception)
{
throw;
}
return this.Jsonp(token);
}
Now, I have an encrypted "token" which has been encrypted using the widget host's public key, and signed using the widget consumer's private key. This "token" is passed back to the widget via JSONP from the consuming server.
My widget then sends this "token" (still as JSONP) to it's host server. The widget hosting server has decrypting logic which looks something like this.
public Message DecryptAndVerify(Packet packet, string requestIP)
{
if (packet == null) throw new ArgumentNullException("packet");
if (requestIP == null) throw new ArgumentNullException("requestIP");
Message message = new Message();
try
{
// Decrypt using the widget host's private key
RSAEncryption decrypto = new RSAEncryption("MyPrivateKey");
// Verify the signature using the "trust's" public key
// This is important because like you'll notice, I get the trust name
// from the encrypted packet. I then maintain a "trust store" mapping
// in my web.config, or SQL server
RSAEncryption verifyo = new RSAEncryption(GetPublicKeyFromTrust(packet.Trust));
string decryptedJson = decrypto.DecryptString(packet.EncryptedData);
// Verify the signature
if (!verifyo.Verify(decryptedJson, packet.Signature))
{
Exception ex = new Exception("Secure packet was not verified. Tamper evident");
throw ex;
}
// If the message is encrypted correctly, turn it into a message object
message = decryptedJson.FromJson<Message>();
// Verify the ip
if (message.EncryptionServerIP != requestIP)
{
Exception ex = new Exception("Request IP does not match encryption IP. Tamper evident");
throw ex;
}
// Verify the time
if ((DateTime.Now - message.EncryptionTime).Seconds > 30)
{
Exception ex = new Exception("Secure packet is too old");
throw ex;
}
}
catch (Exception ex)
{
throw ex;
}
return message;
}
The idea is that the JavaScript widget determines the secure actions the end user wants to take. Then it calls back to it's host (using the handler path provided by the consumer) and requests an encrypted token. That token contains the IP address of the caller, a timestamp, the current AD username, and a bundle of actions to be completed. Once the widget receives the token, it passes it over to it's own host server at which point the server checks to make sure that it is
Signed and encrypted properly according to predefined trusts
Not older than 30 seconds
From the same IP as the initial request to the consumer's server
After I determine those checks to be valid I can act on the user's actions by creating a WindowsPrincipal identity from the string username like this:
WindowsPrincipal pFoo = new WindowsPrincipal(new WindowsIdentity("username"));
bool test = pFoo.IsInRole("some role");
All said and done, I have established a trusted request from the widget consumer, and I no longer have to prompt for authentication.
Hopefully this helps you out. It's been running in my internal environment for about a month of QA and it's it's working great so far.