I am using RSA in javascript from: http://www-cs-students.stanford.edu/~tjw/jsbn/
And on their demo page, it is able to generate the keys required: http://www-cs-students.stanford.edu/~tjw/jsbn/rsa2.html
But what I don't understand, is how to turn their key, which is a few variables, of hex strings, into a string that looks like a private/public key string.
Their output looks like this...
Modulus (hex):
a32464be9bef16a6186a7f29d5ebc3223346faab91ea10cc00e68ba26322a1b0
3dc3e1ec61832fca37ed84018db73ae6b79bd8f3fa2945c8606766402658d0c1
Public exponent (hex, F4=0x10001):
10001
Private exponent (hex):
c3f5730d81402e7453df97df2895884e0c49b5cf5ff54737c3dd28dc6537b3fd
8b0eb2fd82148ebbf81fe16128ec1ebf39fd9a6e62d42aad245b172f4ed8661
P (hex):
f542cdc91f73747ecc20076962a2ed91749b8e0af66693ba6f67dd92f99b1533
Q (hex):
aa491917d8b05a75db7a5c1f6b592b0f0a9bb30a530cbef44ae233b9bf3d5a3b
D mod (P-1) (hex):
e5bfbea639201e70e926d7ca90ebaf4022cbd533cfbe2784edf78e48b029e6a1
D mod (Q-1) (hex):
3e3e17d8fa9083903ed83be21427f4b03bcd6ba523742e3c373ef56f38b2e14f
1/Q mod P (hex):
d93a8828e23458c2115fc1dd003274485af1483e8ec35866d5cffd214c5f853e
I expect a private key to look something like this...
-----BEGIN RSA PRIVATE KEY-----
MIIBOQIBAAJBALH5w2R5Zl3hHoH7zDF6i5IbO7W1Ry9bT2bxTy2sJa7mZC+p3rQG
YNrX+M/KuOUuVqFaohrWLwU28+VlFjFHG5MCAwEAAQJAFZCi+Viwa62saamd+1zS
7pg4KvNVNcrFmz6gDnOueTujcBdaNfxOzWy39SxSOtPe62qYHPculci/QiJfLvOf
MQIhAOqxj69QPRqervf6ARMI1zV0+UAGA1tWgOeU7NH0U9vLAiEAwiIJu+986PKT
qmjAKDFx3OJxUuEay8kJ5apDjC6s1lkCIBVRdazGDBbb7SbHRcu11N6dNnrTUQC9
9c2TYIOdvvRLAiB7bbDKsKW2ZiTEv/0MkQNX8REkJMMothV41BxGUJbLYQIgF/yA
54gqfRtj3aPB6lDETMOZbZIWgNpn8hU2kFGdlIs=
-----END RSA PRIVATE KEY-----
The private and public keys you are familiar with are in a format called PEM. PEM refers to the header and footer wrapping base64 encoded data.
The encoded binary data is in the DER (Distinguished Encoding Rules) format of ASN.1 (Abstract Syntax Notation One).
Here's a javascript ASN.1 decoder.
http://lapo.it/asn1js/
The public and private keys contain the modulus and either the public or private exponent respectively.
The following code came from a couple authors (listed in the comments), compiled on Ohloh Code (http://code.ohloh.net/file?fid=gxO4OHIDEgc0cAHG4K2iptOVOUY&cid=b0rvFixqcq4&s=&fp=285256&mp=&projSelected=true#L0). All credit due to them for their hard work. But the compilation did the trick for me. Add this as a .js library and then you can call it from your main routine like this:
publicPem = publicPEM();
privatePemUnencrypted = privatePEM(); //you will probably want to encrypt this
The output is in PEM format.
Codez:
//From git://github.com/titanous/pem-js.gitmaster
var ASNIntValue, ASNLength, int2hex;
function privatePEM() {
var encoded;
encoded = '020100';
encoded += ASNIntValue(this.n, true);
encoded += ASNIntValue(this.e, false);
encoded += ASNIntValue(this.d, false);
encoded += ASNIntValue(this.p, true);
encoded += ASNIntValue(this.q, true);
encoded += ASNIntValue(this.dmp1, true);
encoded += ASNIntValue(this.dmq1, false);
encoded += ASNIntValue(this.coeff, false);
encoded = '30' + ASNLength(encoded) + encoded;
return "-----BEGIN RSA PRIVATE KEY-----\n" + encode64(chars_from_hex(encoded)) + "\n-----END RSA PRIVATE KEY-----";
};
function publicPEM() {
var encoded;
encoded = ASNIntValue(this.n, true);
encoded += ASNIntValue(this.e, false);
encoded = '30' + ASNLength(encoded) + encoded;
encoded = '03' + ASNLength(encoded, 1) + '00' + encoded;
encoded = '300d06092a864886f70d0101010500' + encoded;
encoded = '30' + ASNLength(encoded) + encoded;
return "-----BEGIN PUBLIC KEY-----\n" + encode64(chars_from_hex(encoded)) + "\n-----END PUBLIC KEY-----";
};
RSAKey.prototype.parsePEM = function(pem) {
pem = ASN1.decode(Base64.unarmor(pem)).sub;
return this.setPrivateEx(pem[1].content(), pem[2].content(), pem[3].content(), pem[4].content(), pem[5].content(), pem[6].content(), pem[7].content(), pem[8].content());
};
ASNIntValue = function(integer, nullPrefixed) {
integer = int2hex(integer);
if (nullPrefixed) {
integer = '00' + integer;
}
return '02' + ASNLength(integer) + integer;
};
ASNLength = function(content, extra) {
var length;
if (!(typeof extra !== "undefined" && extra !== null)) {
extra = 0;
}
length = (content.length / 2) + extra;
if (length > 127) {
length = int2hex(length);
return int2hex(0x80 + length.length / 2) + length;
} else {
return int2hex(length);
}
};
int2hex = function(integer) {
integer = integer.toString(16);
if (integer.length % 2 !== 0) {
integer = '0' + integer;
}
return integer;
};
/* CryptoMX Tools - Base64 encoder/decoder
* http://www.java2s.com/Code/JavaScriptDemo/Base64EncodingDecoding.htm
*
* Copyright (C) 2004 - 2006 Derek Buitenhuis
* Modified February 2009 by TimStamp.co.uk
* GPL v2 Licensed
*/
function encode64(a){a=a.replace(/\0*$/g,"");var b="",d,e,g="",h,i,f="",c=0;do{d=a.charCodeAt(c++);e=a.charCodeAt(c++);g=a.charCodeAt(c++);h=d>>2;d=(d&3)<<4|e>>4;i=(e&15)<<2|g>>6;f=g&63;if(isNaN(e))i=f=64;else if(isNaN(g))f=64;b=b+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(h)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(d)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(i)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(f)}while(c<
a.length);a="";b=b.split("");for(c=0;c<b.length;c++){if(c%64==0&&c>0)a+="\n";a+=b[c]}b.join();b=a%4;for(c=0;c<b;c++)a+="=";return a}
function decode64(a){var b="",d,e,g="",h,i="",f=0;a=a.replace(/[^A-Za-z0-9\+\/\=\/n]/g,"");do{d="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(a.charAt(f++));e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(a.charAt(f++));h="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(a.charAt(f++));i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(a.charAt(f++));d=d<<2|e>>4;e=(e&15)<<4|h>>2;g=(h&3)<<
6|i;b+=String.fromCharCode(d);if(h!=64)b+=String.fromCharCode(e);if(i!=64)b+=String.fromCharCode(g)}while(f<a.length);return b=b.replace(/\0*$/g,"")};
/* JavaScript ASCII Converter
* http://www.vortex.prodigynet.co.uk/misc/ascii_conv.html
*
* TPO 2001/2004
* Modified Feb 2009 by Tim Stamp (timstamp.co.uk)
* License Unknown
*/
function chars_from_hex(a){var c="";a=a.replace(/^(0x)?/g,"");a=a.replace(/[^A-Fa-f0-9]/g,"");a=a.split("");for(var b=0;b<a.length;b+=2)c+=String.fromCharCode(parseInt(a[b]+""+a[b+1],16));return c};
Using the link suggested by #borkencode (http://lapo.it/asn1js/) I have been able to successfully extract all the components of the public key, and also the relevant components from private keys.
Code to test extraction of numerical portions of keys
// copy and paste into console whilst on `http://lapo.it/asn1js/`
// so that dependancies are met
var getParts, pri, pub;
getParts = function(key) {
var end, current, output, crypt, slice, sa, so, start, tree, _i, _len;
// strip out the BEGIN/END tags from the input keys
crypt = key.replace(/^-.+/mg, '');
// decode the input text into ASN1 object
tree = ASN1.decode(Base64.decode(crypt));
// set root object based on type of key
if (key.match(/PUBLIC KEY-/)) {
root = tree.sub[1].sub[0].sub;
} else if (key.match(/PRIVATE KEY-/)) {
root = tree.sub;
} else {
console.error("Not happening");
return;
}
// initialize empty array to populate with results
output = [];
// loop through the root ASN1 object
for (_i = 0, _len = root.length; _i < _len; _i++) {
// get the current object
current = root[_i];
// set the start and end positions in the stream
start = current.stream.pos + current.header;
end = start + current.length;
// cut the required compnent out of the stream
slice = current.stream.enc.slice(start, end);
// format the slice in HEX, joining as string
// and replacing leading `00`s
str = slice.map(function(i) {
return ("0" + i.toString(16)).replace(/.(..)/, "$1");
}).join('').replace(/^(00)+/, '');
// add to output array
output.push(str);
}
// send the results!
return output;
};
pri = "-----BEGIN RSA PRIVATE KEY-----\n"+
"MIIBOgIBAAJBAMpZrx0gTluJEu6+fop1e60lwbnlBD6kHvoRx85GBhUgD8SQknjc\n"+
"LcU2qqM/pV9ZX8MV8x49h2mzrmRyH7kDmpcCAwEAAQJAYf2GYMt5Rrids4IKk5CL\n"+
"IPFs3FH8eT1PRvh/UvP0FBwDMZu/Q4m+3PNTM3ARQhFuCvWgCalMmZkyVx0HYRLe\n"+
"4QIhAOaaQm+b/bSoHqolvVTcyfBL09rrLFZhgGkETX3R6cVRAiEA4KLdUm97YBxP\n"+
"T6/jkn/P7K8SUWEO9o9u8Bif1UKQB2cCIETqoSQ92EqfW9q5wKWV/nvkDYKFehCu\n"+
"vvOjp40MqPKhAiA2sPBZpbLQD5Rvvk8V1/Bzm5xGG+9csEc+RYCEl5QheQIhAKgi\n"+
"Xb3zY9lqtpX/mgTIrW6RPB3GocviJOibqtpfNxRU\n"+
"-----END RSA PRIVATE KEY-----";
pub = "-----BEGIN PUBLIC KEY-----\n"+
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMpZrx0gTluJEu6+fop1e60lwbnlBD6k\n"+
"HvoRx85GBhUgD8SQknjcLcU2qqM/pV9ZX8MV8x49h2mzrmRyH7kDmpcCAwEAAQ==\n"+
"-----END PUBLIC KEY-----";
console.dir(getParts(pri));
console.dir(getParts(pub));
Output to console:
0: ""
1: "ca59af1d204e5b8912eebe7...d8769b3ae64721fb9039a97"
2: "010001"
3: "61fd8660cb7946b89db3820...94c999932571d076112dee1"
4: "e69a426f9bfdb4a81eaa25b...c56618069044d7dd1e9c551"
5: "e0a2dd526f7b601c4f4fafe...ef68f6ef0189fd542900767"
6: "44eaa1243dd84a9f5bdab9c...a10aebef3a3a78d0ca8f2a1"
7: "36b0f059a5b2d00f946fbe4...f5cb0473e45808497942179"
8: "a8225dbdf363d96ab695ff9...1cbe224e89baada5f371454"
------------------------------------------------------
0: "ca59af1d204e5b8912eebe7...d8769b3ae64721fb9039a97"
1: "010001"
I have tested the values, using them as inputs to encrypt, and decrypt messages with my javascript library. What I really want to be able to do now, is to create the .pem and .pub files by going the other way, from numbers, to formatted text.
The closest I have found so far, is a PHP function, which I might translate to javascript. Details here: http://pumka.net/2009/12/19/reading-writing-and-converting-rsa-keys-in-pem-der-publickeyblob-and-privatekeyblob-formats/
//Encode key sequence
$modulus = new ASNValue(ASNValue::TAG_INTEGER);
$modulus->SetIntBuffer($Modulus);
$publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
$publicExponent->SetInt($PublicExponent);
$keySequenceItems = array($modulus, $publicExponent);
$keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
$keySequence->SetSequence($keySequenceItems);
//Encode bit string
$bitStringValue = $keySequence->Encode();
$bitStringValue = chr(0x00) . $bitStringValue; //Add unused bits byte
$bitString = new ASNValue(ASNValue::TAG_BITSTRING);
$bitString->Value = $bitStringValue;
//Encode body
$bodyValue = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00" . $bitString->Encode();
$body = new ASNValue(ASNValue::TAG_SEQUENCE);
$body->Value = $bodyValue;
//Get DER encoded public key:
$PublicDER = $body->Encode();
For answer to your question, please have a look at the RSA algorithm for the actual formulae . For the complexity part, its better to leave the transformation to the JS
In case it helps, I used this code for my application and it worked wonders :)
Related
This is the function used to encrypt in java
public static String encryptionFunction(String fieldValue, String pemFileLocation) {
try {
// Read key from file
String strKeyPEM = "";
BufferedReader br = new BufferedReader(new FileReader(pemFileLocation));
String line;
while ((line = br.readLine()) != null) {
strKeyPEM += line + "\n";
}
br.close();
String publicKeyPEM = strKeyPEM;
System.out.println(publicKeyPEM);
publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "").replaceAll("\\s", "");;
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
// byte[] encoded = Base64.decode(publicKeyPEM);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubKey = (PublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] cipherData = cipher.doFinal(fieldValue.getBytes());
if (cipherData == null) {
return null;
}
int len = cipherData.length;
String str = "";
for (int i = 0; i < len; i++) {
if ((cipherData[i] & 0xFF) < 16) {
str = str + "0" + java.lang.Integer.toHexString(cipherData[i] & 0xFF);
} else {
str = str + java.lang.Integer.toHexString(cipherData[i] & 0xFF);
}
}
return str.trim();
} catch (Exception e) {
System.out.println("oops2");
System.out.println(e);
}
return null;
}
I want the equivalent of this in javascript/Nodejs, i tried this:
import * as NodeRSA from 'node-rsa';
private encryptionFunction(fieldValue: string , pemkey: string) : string {
const rsa = new NodeRSA(pemkey);
const encrypted= rsa.encrypt(fieldValue , 'hex')
return encrypted
}
But the output size of both functions is the same, but apparently the encryption type is wrong.
Node-RSA applies OAEP (here) as padding by default, so the PKCS#1 v1.5 padding used in the Java code must be explicitly specified. This has to be added after key import and before encryption:
rsa.setOptions({ encryptionScheme: 'pkcs1' });
Alternatively, the padding can be specified directly during key import:
const rsa = new NodeRSA(pemkey, { encryptionScheme: 'pkcs1' });
With this change, both codes are functionally identical.
Regarding testing: Keep in mind that RSA encryption is not deterministic, i.e. given the same input (key, plaintext), each encryption provides a different ciphertext. Therefore, the ciphertexts of both (functionally identical) codes will be different even if the input is identical. So this is not a bug, but the expected behavior.
How then can the equivalence of both codes be proved? E.g. by decrypting both ciphertexts with the same code/tool.
During my research I've found these information but it seems like they are not really matching to my problem.
http://www.cplusplus.com/forum/beginner/31776/
Base 10 to base n conversions
https://cboard.cprogramming.com/cplusplus-programming/83808-base10-base-n-converter.html
So I'd like to implement a custom Base64 to BaseN encoding and decoding using C++.
I should be able to convert a (Base64)-string like "IloveC0mpil3rs" to a custom Base (e.g Base4) string like e.g "10230102010301" and back again.
Additional I should be able to use a custom charset (alphabet) for the base values like the default one probably is "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".
So I should be able to use a shuffled one like e.g this (kind of encoding :) ): "J87opBEyWwDQdNAYujzshP3LOx1T0XK2e+ZrvFnticbCS64a9/Il5GmgVkqUfRMH".
I thought about translating the convertBase-function below from javascript into C++ but I'm obviously a beginner and got big problems, so I got stuck right there because my code is not working as expected and I can not find the error:
string encoded = convertBase("Test", 64, 4); // gets 313032130131000
cout << encoded << endl;
string decoded = convertBase(encoded, 4, 64); // error
cout << decoded << endl;
C++ code: (not working)
std::string convertBase(string value, int from_base, int to_base) {
string range = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/";
string from_range = range.substr(0, from_base),
to_range = range.substr(0, to_base);
int dec_value = 0;
int index = 0;
string reversed(value.rbegin(), value.rend());
for(std::string::iterator it = reversed.begin(); it != reversed.end(); ++it) {
index++;
char digit = *it;
if (!range.find(digit)) return "error";
dec_value += from_range.find(digit) * pow(from_base, index);
}
string new_value = "";
while (dec_value > 0) {
new_value = to_range[dec_value % to_base] + new_value;
dec_value = (dec_value - (dec_value % to_base)) / to_base;
}
return new_value;
}
javascript code: (working)
function convertBase(value, from_base, to_base) {
var range = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/'.split('');
var from_range = range.slice(0, from_base);
var to_range = range.slice(0, to_base);
var dec_value = value.split('').reverse().reduce(function (carry, digit, index) {
if (from_range.indexOf(digit) === -1) throw new Error('Invalid digit `'+digit+'` for base '+from_base+'.');
return carry += from_range.indexOf(digit) * (Math.pow(from_base, index));
}, 0);
var new_value = '';
while (dec_value > 0) {
new_value = to_range[dec_value % to_base] + new_value;
dec_value = (dec_value - (dec_value % to_base)) / to_base;
}
return new_value || '0';
}
let encoded = convertBase("Test", 64, 4)
console.log(encoded);
let decoded = convertBase(encoded, 4, 64)
console.log(decoded);
Any help how to fix my code would be very appreciated!
I have a simple node.js script
var text = "Hello!";
var serverSeed = "d8818b38a14e7461e87301ad4b9809b558bcbca816b650cd470452e018ada255";
var crypto = require('crypto');
var hash = crypto.createHmac('sha512', serverSeed).update(text).digest('hex');
console.log(hash);
I also have the C# program
using System;
using System.Text;
using System.Security.Cryptography;
public class Program
{
public static byte[] StringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
public static void Main()
{
var serverSeed = StringToByteArray("d8818b38a14e7461e87301ad4b9809b558bcbca816b650cd470452e018ada255");
using (var sha = new HMACSHA512(serverSeed))
{
var hash = sha.ComputeHash(Encoding.ASCII.GetBytes("Hello!"));
Console.WriteLine(ByteArrayToString(hash));
}
}
}
(runable version here)
I get from the node.js program
99e3b20acaa9c7674f074da950945ee897876b0afc02121d5a89fa581081465f3e01a084e9b05bed729b7fbdc1d485fb38af7d6f501cbc258b6c66add54410ba
And from the C# program
73250817a927f394b0912afcece47b8c12aeaed31892c64116ae9dd0d407f6e31d5c062d65f68a3cae09a8acb14a7cef1f6afd99f5a22f2b73e46a991fcd079a
What am I doing wrong to cause this difference?
Your C# code is converting the hex characters in your seed to a byte array based on the what the characters represent in hexadecimal format.
But your node code is passing the seed as a string which is converting the characters to a byte.
So for example, your C# code is converting a to a byte with the value of 10 but your node code would be converting a to a byte with the value of 97
Your node code either needs to convert the hex in your string to a Buffer based on their hex values as you are doing in C#.
var text = "Hello!";
var serverSeed = "d8818b38a14e7461e87301ad4b9809b558bcbca816b650cd470452e018ada255";
var crypto = require('crypto');
var buff = new Buffer(seed, "hex")
var hash = crypto.createHmac('sha512', buff).update(text).digest('hex');
console.log(hash);
Or in C# instead of converting hex to bytes you can get a byte array representing the actually characters the seed using GetBytes from a System.Text.Encoding instance.
var serverSeed = Encoding.ASCII.GetBytes("d8818b38a14e7461e87301ad4b9809b558bcbca816b650cd470452e018ada255");
using (var sha = new HMACSHA512(serverSeed))
{
var hash = sha.ComputeHash(Encoding.ASCII.GetBytes("Hello!"));
Console.WriteLine(ByteArrayToString(hash));
}
Most likely you intended to pass the serverSeed as a buffer in the node.js code.
I try to sign messages in javascript before sending to a PHP application.
The PHP application must check the signature to be sure it's not false.
In javascript I use cryptico.js.
This is the js function for signing messages
var sign = function(passphrase, text) {
signingkey = cryptico.generateRSAKey(passphrase, 2048);
signString = cryptico.b16to64(signingkey.signString(text, "sha256"));
return signString;
}
This is the function for getting the public key:
var getPublicKey = function(passphrase) {
var rsaKey = cryptico.generateRSAKey(passphrase, 2048);
return = cryptico.publicKeyString(rsaKey);
}
For example, for the message "message" and the passphrase "test2" the public key and signature are
qH/J3/gvF/h5U02uPyC9Qzn/hHEV5DzB9nFfqk5zbQqHdInVe4sfL+npa+4fjLGrBU30Iuvcr+o9paEjzpH5dY48cq6JHqz1RyJ0CQIc2Jr5+sS4eL1ZIjxWlyN1pKMR+4aE2rlDAad56Ad1cytiaHuVvyK/gdtbKiuGroSQhJ1EVfZ60m3NIqnqmpi5Zdsnmzny4VH/d66BcGXxGaGaUaqFn0WTypuwIMZMMtzZEK7peKoaW4H4rfkfdrKcD8AaT+z9v5lLGkTl0NcZZ4LN9sSUzsHNfyAFK6cSXo/73z0tDAlGb5K+yWV6UHoYW1rcoIsxlNRZM6/6FYgMXbbfow==
XbF4O6v6oadEQGtdpQ7d54Q2JB9/ZEXEUH3S1FMn4E/PSqk7HLXjG4tNfuiUBa5eS8kYV49gwC8Yr+mn6YUAHt+K9lHPSsmltWoiHNOaas4aqai9nlyeft4TYYhP+GYbQfw+3n2TcO39s6M0vw0m0a8AX9JfF02JwCUhP4bu4dzG6Bl5dj000TbUkric14Jyurp8OHmmMvKW62TvXPhNOW39+wS1Qkfn9Bxmzi8UEVSVe3wP45JWZNgmgeGnpubDhD05FJEDErfVtZ/DRKD81q5YRd4X4cCkeDPDcJLgKW1jkCsA7yBqESXPDSkkrVUM06A9qMFUwk4mRI88fZ8ryQ==
I'm asking me how to verify it in php?
I tryed something like:
$rsa = new Crypt_RSA();
$rsa->loadKey('qH/J3/gvF/h5U02uPyC9Qzn/hHEV5DzB9nFfqk5zbQqHdInVe4sfL+npa+4fjLGrBU30Iuvcr+o9paEjzpH5dY48cq6JHqz1RyJ0CQIc2Jr5+sS4eL1ZIjxWlyN1pKMR+4aE2rlDAad56Ad1cytiaHuVvyK/gdtbKiuGroSQhJ1EVfZ60m3NIqnqmpi5Zdsnmzny4VH/d66BcGXxGaGaUaqFn0WTypuwIMZMMtzZEK7peKoaW4H4rfkfdrKcD8AaT+z9v5lLGkTl0NcZZ4LN9sSUzsHNfyAFK6cSXo/73z0tDAlGb5K+yWV6UHoYW1rcoIsxlNRZM6/6FYgMXbbfow=='); // public key
echo $rsa->verify('message', 'XbF4O6v6oadEQGtdpQ7d54Q2JB9/ZEXEUH3S1FMn4E/PSqk7HLXjG4tNfuiUBa5eS8kYV49gwC8Yr+mn6YUAHt+K9lHPSsmltWoiHNOaas4aqai9nlyeft4TYYhP+GYbQfw+3n2TcO39s6M0vw0m0a8AX9JfF02JwCUhP4bu4dzG6Bl5dj000TbUkric14Jyurp8OHmmMvKW62TvXPhNOW39+wS1Qkfn9Bxmzi8UEVSVe3wP45JWZNgmgeGnpubDhD05FJEDErfVtZ/DRKD81q5YRd4X4cCkeDPDcJLgKW1jkCsA7yBqESXPDSkkrVUM06A9qMFUwk4mRI88fZ8ryQ==') ? 'verified' : 'unverified';
I think the signature and/or public key are not formated correctly for php. Any idea?
Thank you in advance,
[EDIT]
I'm not sure the signature is correct. If I use the js function cryptico.b64to16(signature), the signature will be somethink like :
5db1783babfaa1a744406b5da50edde78436241f7f6445c4507dd2d45327e04fcf4aa93b1cb5e31b8b4d7ee89405ae5e4bc918578f60c02f18afe9a7e985001edf8af651cf4ac9a5b56a221cd39a6ace1aa9a8bd9e5c9e7ede1361884ff8661b41fc3ede7d9370edfdb3a334bf0d26d1af005fd25f174d89c025213f86eee1dcc6e81979763d34d136d492b89cd78272baba7c3879a632f296eb64ef5cf84d396dfdfb04b54247e7f41c66ce2f141154957b7c0fe3925664d82681e1a7a6e6c3843d3914910312b7d5b59fc344a0fcd6ae5845de17e1c0a47833c37092e0296d63902b00ef206a1125cf0d2924ad550cd3a03da8c154c24e26448f3c7d9f2bc9
I am not sure about the format of the param key of $rsa->verify. I tryed to add the prefix ssh-rsa. But it do not works better.
So I tryed the to signature format and the to key. The message is each time "unverified"
Thanks #neubert, it's du to PSS signature.
So here is the solution.
I use:
phpseclib : PHP lib used to validate message
jsrsasign : JS lib used to sign message
jsencrypt : JS lib to create private and public key
First, generate the keys in JS:
crypt = new JSEncrypt({default_key_size: 512});
var key = crypt.getKey();
var publicKey = key.getPublicKey();
var privateKey = key.getPrivateKey();
Secondly, create the signature:
var rsa = new RSAKey();
rsa.readPrivateKeyFromPEMString(privateKey);
var hSig = rsa.signStringPSS('message', 'sha1');
var signature = linebrk(hSig, 64);
console.log(signature);
By default the signature is not in the good format. We have to encode hSig in base 64 with the function base64Chars
var base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
b16to64 = function(h) {
var i;
var c;
var ret = "";
if(h.length % 2 == 1)
{
h = "0" + h;
}
for (i = 0; i + 3 <= h.length; i += 3)
{
c = parseInt(h.substring(i, i + 3), 16);
ret += base64Chars.charAt(c >> 6) + base64Chars.charAt(c & 63);
}
if (i + 1 == h.length)
{
c = parseInt(h.substring(i, i + 1), 16);
ret += base64Chars.charAt(c << 2);
}
else if (i + 2 == h.length)
{
c = parseInt(h.substring(i, i + 2), 16);
ret += base64Chars.charAt(c >> 2) + base64Chars.charAt((c & 3) << 4);
}
while ((ret.length & 3) > 0) ret += "=";
return ret;
}
To finish we validate in PHP. We assume the signature and the public key are stored in the vars with the same name:
$rsa = new Crypt_RSA();
$rsa->loadKey($publickey); // public key;
echo $rsa->verify('message', base64_decode($signature)) ? 'verified' : 'unverified';
I'm trying to read a binary file from the filesystem and then base64 encode it in JavaScript. I'm using the FileReader API to read the data and the base64 encoder found here.
The code I have seems close to working, the problem is that the generated base64 data is wrong. Here's what I've got so far:
function saveResource() {
var file = $(".resourceFile")[0].files[0];
var reader = new FileReader();
reader.onload = function(evt) {
var fileData = evt.target.result;
var bytes = new Uint8Array(fileData);
var binaryText = '';
for (var index = 0; index < bytes.byteLength; index++) {
binaryText += String.fromCharCode( bytes[index] );
}
console.log(Base64.encode(binaryText));
};
reader.readAsArrayBuffer(file);
};
Here's the file I'm testing with (it's a 100x100 blue square):
According to an online base64 decoder/encoder, this file should encode to:
/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABkAGQDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDxyiiiv3E8wKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//Z
...but instead what I'm getting out of the JavaScript is:
w7/DmMO/w6AAEEpGSUYAAQIAAAEAAQAAw7/DmwBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDLDv8ObAEMBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMsO/w4AAEQgAZABkAwEiAAIRAQMRAcO/w4QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoLw7/DhADCtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDLCgcKRwqEII0LCscOBFVLDkcOwJDNicsKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5esKDwoTChcKGwofCiMKJworCksKTwpTClcKWwpfCmMKZwprCosKjwqTCpcKmwqfCqMKpwqrCssKzwrTCtcK2wrfCuMK5wrrDgsODw4TDhcOGw4fDiMOJw4rDksOTw5TDlcOWw5fDmMOZw5rDocOiw6PDpMOlw6bDp8Oow6nDqsOxw7LDs8O0w7XDtsO3w7jDucO6w7/DhAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgvDv8OEAMK1EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIywoEIFELCkcKhwrHDgQkjM1LDsBVicsORChYkNMOhJcOxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXrCgsKDwoTChcKGwofCiMKJworCksKTwpTClcKWwpfCmMKZwprCosKjwqTCpcKmwqfCqMKpwqrCssKzwrTCtcK2wrfCuMK5wrrDgsODw4TDhcOGw4fDiMOJw4rDksOTw5TDlcOWw5fDmMOZw5rDosOjw6TDpcOmw6fDqMOpw6rDssOzw7TDtcO2w7fDuMO5w7rDv8OaAAwDAQACEQMRAD8Aw7HDiijCosK/cTzDgMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooAMKiwoooA8O/w5k=
If i had to hazard a guess I'd say that the issue has something to do with nonprintable characters in the binary data (if I encode a plaintext document, that works fine). But what's the best approach to work around the issue?
Edit
It looks like this may be a problem with the base64 library itself (or if not that, then with how the Uint8Array is unpackaged into a string for the library call). If I use the browser's btoa() function instead, and pass it the Uint8Array binaryText directly, that works. Too bad that function doesn't exist in all browsers.
And Google to the rescue. I found the following code, which takes the input data as a plain array of "bytes" (numbers between 0 and 255, inclusive; also works fine if the Uint8Array is passed to it directly), and added it to the library I was using:
//note: it is assumed that the Base64 object has already been defined
//License: Apache 2.0
Base64.byteToCharMap_ = null;
Base64.charToByteMap_ = null;
Base64.byteToCharMapWebSafe_ = null;
Base64.charToByteMapWebSafe_ = null;
Base64.ENCODED_VALS_BASE =
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'abcdefghijklmnopqrstuvwxyz' +
'0123456789';
/**
* Our default alphabet. Value 64 (=) is special; it means "nothing."
* #type {string}
*/
Base64.ENCODED_VALS = Base64.ENCODED_VALS_BASE + '+/=';
Base64.ENCODED_VALS_WEBSAFE = Base64.ENCODED_VALS_BASE + '-_.';
/**
* Base64-encode an array of bytes.
*
* #param {Array.<number>|Uint8Array} input An array of bytes (numbers with
* value in [0, 255]) to encode.
* #param {boolean=} opt_webSafe Boolean indicating we should use the
* alternative alphabet.
* #return {string} The base64 encoded string.
*/
Base64.encodeByteArray = function(input, opt_webSafe) {
Base64.init_();
var byteToCharMap = opt_webSafe ?
Base64.byteToCharMapWebSafe_ :
Base64.byteToCharMap_;
var output = [];
for (var i = 0; i < input.length; i += 3) {
var byte1 = input[i];
var haveByte2 = i + 1 < input.length;
var byte2 = haveByte2 ? input[i + 1] : 0;
var haveByte3 = i + 2 < input.length;
var byte3 = haveByte3 ? input[i + 2] : 0;
var outByte1 = byte1 >> 2;
var outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4);
var outByte3 = ((byte2 & 0x0F) << 2) | (byte3 >> 6);
var outByte4 = byte3 & 0x3F;
if (!haveByte3) {
outByte4 = 64;
if (!haveByte2) {
outByte3 = 64;
}
}
output.push(byteToCharMap[outByte1],
byteToCharMap[outByte2],
byteToCharMap[outByte3],
byteToCharMap[outByte4]);
}
return output.join('');
};
/**
* Lazy static initialization function. Called before
* accessing any of the static map variables.
* #private
*/
Base64.init_ = function() {
if (!Base64.byteToCharMap_) {
Base64.byteToCharMap_ = {};
Base64.charToByteMap_ = {};
Base64.byteToCharMapWebSafe_ = {};
Base64.charToByteMapWebSafe_ = {};
// We want quick mappings back and forth, so we precompute two maps.
for (var i = 0; i < Base64.ENCODED_VALS.length; i++) {
Base64.byteToCharMap_[i] =
Base64.ENCODED_VALS.charAt(i);
Base64.charToByteMap_[Base64.byteToCharMap_[i]] = i;
Base64.byteToCharMapWebSafe_[i] =
Base64.ENCODED_VALS_WEBSAFE.charAt(i);
Base64.charToByteMapWebSafe_[
Base64.byteToCharMapWebSafe_[i]] = i;
}
}
};
The full code for the library containing the above functions is available here, but in its non-modified form it appears to depend upon a number of other libraries. The slightly hacked-up version above should work for anyone who just needs a quick fix for this issue.
Treat the binary as arraybuffer, this is independent from any character encoding. Your blue-square (.jpg) has 361 native bytes, means octets from 0..255 (decimal) and they are no characters!
It means: Use ArrayBuffer for encoding this to Base64 with well known base64-algorithm.
With Perl back to origin, shows the blue-square as above:
my $fh = IO::File->new;
$fh->open("d:/tmp/x.jpg", O_BINARY|O_CREAT|O_RDWR|O_TRUNC) or die $!;
$fh->print(decode_base64("/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBD
AQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABkAGQDASIAAhEBAxEB/8QAFQABAQAA
AAAAAAAAAAAAAAAAAAf/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFgEBAQEAAAAAAAAAAAAAAAAAAAUH/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMR
AD8AjgDcUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//2Q==
"));
$fh->close;