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');
}
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.
Is there a coldfusion alternaitive to this php function: openssl_verify:
openssl_verify() verifies that the signature is correct for the
specified data using the public key associated with pub_key_id. This
must be the public key corresponding to the private key used for
signing.
I've looked all over but there doesn't seem to be any. Thanks in advance for any info?
There are no built in functions, AFAIK. However, java supports signature verification, which you could adapt with a bit of java code.
Convert the data you want to verify into binary. The exact steps depends on what you are verifying, but say it is a physical file:
dataBytes = fileReadBinary( "c:\path\someFile.zip" );
Decode the signature value into binary. Again, the "how" depends on the signature format. If it is a base64 encoded string:
signatureBytes = binaryDecode( base64SignatureString, "base64" );
Load the certificate from your keystore (or from a file) and extract the public key:
// Example: "C:\ColdFusion\jre\lib\security\cacerts"
fis = createObject("java", "java.io.FileInputStream").init( pathToKeyStore );
keyStore = createObject("java", "java.security.KeyStore").getInstance("JKS");
// Default keystore password is "changeit" (do not keep the default. change it)
keyStore.load(fis, keyStorePassword.toCharArray());
publicKey = keyStore.getCertificate( "yourCertAlias" ).getPublicKey();
Create a Signature object to perform the verification. Initialize it with the appropriate algorithm (ie SHA1withRSA, etcetera), public key and the data to verify:
sign = createObject("java", "java.security.Signature").getInstance("SHA1withRSA");
sign.initVerify( publicKey );
sign.update( dataBytes );
Finally, feed in the signature and verify its status:
isVerified = sign.verify(signatureBytes);
writeDump( isVerified );
For more details, see Lesson: Generating and Verifying Signatures and Weaknesses and Alternatives.
You could attempt using cfexecute along with the OpenSSL CLI. https://www.openssl.org/docs/manmaster/apps/verify.html
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.
The following PHP function verifies that the $data string was signed using $key to create the $signature:
<?php
$result = openssl_verify( $data , $signature , $key , OPENSSL_ALGO_SHA1 );
?>
Is there an equivalent PHP function where I can get the original $data string if I have the correct $signature and $key
I think OP had a misconception about the signing process that I understand because I had it too.
The short answer is: no, there is no such PHP function, because it is impossible.
The digital signature is not just the original message ($data) encrypted with a Private Key.
It's the original message hashed into a "message digest" and then encrypted using a Private Key.
If you were to decrypt the signature with the matching public key, you would actually obtain the "message digest".
However there is no way to obtain the original message from the "message digest" because the hashing process is one way by design.
Verifying the signature is the process of hashing $data with the same hashing function and comparing it to the "message digest" obtained from decrypting the signature with the Public Key.
I guess that's what php function openssl_verify does.
This page helped me understand:
http://www.youdzone.com/signature.html
$data IS the original $data string, that's what the openssl_verify call does - verify that $data is unchanged, using $signature and $key
If you're asking if a $signature can be used to generate the data, then no - even if you have the private key, you still cannot recreate the data.
Imagine if you have a physical document (like a Will), which is signed, and a signature card. While the signature card ($key)allows you to verify the signature is authentic ($signature), the signature cannot be used to recreate the original content of the document (say, for example, if ink is spilled across the top of the document, but not across the signature line) - even if you're the original signer of the document. It can only be used to authenticate THAT document (Will) was, in fact, signed (legally binding).
Digital signatures are a bit more complicated from a math perspective, and provide some subtly different mechanisms for authentication, but in this case the result is the same - a signature cannot be used to recreate an original document, even if you have the signature card ($key), and even if you have the private key.
i will be providing api keys to my partner sites and they will be using code that i give them to to generate "tokens".
these tokens will be automatically present on forms which the partner sites' users will click on and reach my site. when they reach my site, i will need to validate that they indeed came from a partner site.
how do i validate this? the apikey will be secret, but what is presented in the form will NOT be, so it must not be possible for smart users to reverse engineer my algorithm.
EDITA
Option1: I get teh client page to send across md5($apikey.$time) AND $time (in plaintext). When i get it, i use time and my copy of apikey to generate md5($apikey.$time). if it matches and is within 1 hour (or whatever), i let the request proceed.
Option2: I already have $userid, $requestcommandoption coming in as well. I can do the following:
$input = $userid.'-'.$requestcommandoption.'-'.$time;
$encrypted_data = mcrypt_ecb (MCRYPT_3DES, $apikey, $input, MCRYPT_ENCRYPT);
when i get it at my end, i can do:
$decrypted_data = mcrypt_ecb (MCRYPT_3DES, $apikey, $encrypted_data, MCRYPT_DECRYPT);
and then check the 2 inputs if they are the same, and the 3rd if its within 1 hour?
EDITB
How secure does this sound? (code borrowed from http://onlamp.com/pub/a/php/2001/07/26/encrypt.html?page=3)
// on client
$apikey="test123";
$userid = '577';
$requestcommandoption = 'delete-all';
$time = mktime();
echo "time = $time<p>";
$input = $userid.'-'.$requestcommandoption.'-'.$time;
// Encryption Algorithm
$cipher_alg = MCRYPT_RIJNDAEL_128;
// Create the initialization vector for added security.
$iv = mcrypt_create_iv(mcrypt_get_iv_size($cipher_alg, MCRYPT_MODE_ECB), MCRYPT_RAND);
// Encrypt $string
$encrypted_string = mcrypt_encrypt($cipher_alg, $apikey, $input, MCRYPT_MODE_CBC, $iv);
$transmitted = bin2hex($encrypted_string);
// sent from client to server
print "Encrypted string: ".$transmitted."<p>";
// received on server
$encrypted_string = pack("H*" , $transmitted);
$decrypted_string = mcrypt_decrypt($cipher_alg, $apikey, $encrypted_string, MCRYPT_MODE_CBC, $iv);
print "Decrypted string: $decrypted_string";
Looks as if you're implementing something similar to open authentication - the process twitter/facebook etc use for enabling partner sites.
I'd recommend that you take a look at oAuth - http://oauth.net/ - there are plenty of libraries and php samples.
If you really want to do something simple, then assuming you've got a record of the API keys you've handed out, I would write the client script so that it makes an md5 hash of the key with another bit of information on the form - a username for example (lets call the hashed string the request key and the username the username), and I'd include an identifier for the partner (which we'll call partner_id).
So when the form submits it has the requestkey, username and partner_id.
When your server receives the request you can look up the secret key for the partner using the partner_id, then md5 the secret key you've got, with the supplied user name and see if it matches the md5 key sent with the form.
#frank...
[Adding this as a result of your comment]
In order to make the key that is sent over the wire disposable, you can get the client web page to request a temporary session key - your server generates one (using a combination of date + time + a secret word), saves it as a temp key in the partners table (alongside their permanent key) and sends it back to the client. The client app then MD5's this with the permanent key and submits this with the form. You then look up the permanent and temp keys and hash them together and compare the result with the hash you've been sent.
does that seem ok?
What about GUIDs? Of course, you would have to track the issued GUIDs.
http://php.net/manual/en/function.com-create-guid.php
You should check out OAuth and OAuth 2 standards. They're widely used for authorization and many APIs (Facebook, Twitter, etc.)