GCP KMS - Elliptic curve Signature - google-cloud-platform

I am using google cloud KMS to manage my keys. Using JAVA client libs to interact with KMS. I receive byte array as a signature of a message as below
byte[] plaintext = message.getBytes(StandardCharsets.UTF_8);
// Calculate the digest.
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] hash = sha256.digest(plaintext);
// Build the digest object.
Digest digest = Digest.newBuilder().setSha256(ByteString.copyFrom(hash)).build();
// Sign the digest.
AsymmetricSignResponse result = client.asymmetricSign(keyVersionName, digest);
byte[] signature = result.getSignature().toByteArray();
How to get a pair to integers {r, s} as a signature as stated here

R and S are packed into an Ecdsa-Sig-Value ASN.1 structure. The most straightforward way to extract them would be to rely on a library like BouncyCastle that can read the ASN.1 sequence. For example
import java.math.BigInteger;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
private static BigInteger[] extractRandS(byte[] asn1EncodedSignature) {
ASN1Sequence seq = ASN1Sequence.getInstance(asn1EncodedSignature);
BigInteger r = ((ASN1Integer) seq.getObjectAt(0)).getValue();
BigInteger s = ((ASN1Integer) seq.getObjectAt(1)).getValue();
return new BigInteger[]{r, s};
}

Related

Openssl C/C++ programmatically get RSA public key from modulus and exponent

IdPs like Okta are providing public key and exponent in this format.
"n":"sNwO2gzGvmmQH8BKFa--JbsaQrEY7hg9YrJ3lqs_t36cd6EJInE3W0EbmdAHWbZC4-AeMS73BZQsaJqa2UvqWfUTwpVrEVlPOHc0_Tc4VTqLsmuoPaByYOgz5hn3Z_0gYfPq8eGIYuh6QLvKkuYdAWr5yMK0xDof2eFmQ-BoSMjiB4id_c2BjX_TlqxHCDoXtwCD-51R2ZFTNP9PW2ivunDmAD4RCuLjHxnjiB-GmJFGX0KwTp71Ppyd8MYcUFi_ExxOFDWtOqyPzBhWVX0NmxvvujTAUdTa90u29UE0g59W1tbhKQH8LzakWlpaopkqhfDZxeKCo9dDrYhw5NQ1ow"
"e":"AQAB"
Can someone tell me how do I decode the exponent 'AQAB' programmatically to a valid value which can be given as input to RSA structure in C/C++ language? I know AQAB decodes to 010001 in hex but I am looking for a sample in c/c++ sample to do that.
After that I can do:
RSA* rsa = RSA_new();
rsa->e = e;
rsa->n = n;
char* lPublic = BIO_new(BIO_s_mem());
PEM_write_bio_RSAPublicKey(lPublic, rsa);

Exporting EC_POINT using point2hex in ASN1.DER and recreate with Java as an X.509

I am generating ECDSA Prime256 keypair using OpenSSL with C++ and trying to import the hex version of the public key using Java. I pass the byte array I obtain from C++ to the following function in java which expects the byte array to be in an X.509 encoded format.
public static PublicKey getPublicKey(byte[] pk) throws NoSuchAlgorithmException, InvalidKeySpecException {
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pk);
KeyFactory kf = KeyFactory.getInstance(Constant.KEY_FACTORY_TYPE);
PublicKey pub = kf.generatePublic(publicKeySpec);
return pub;
}
I create an elliptic curve key pair using the following function which retuns an EC_KEY*
EC_KEY* generate_keypair() {
EC_KEY *eckey = EC_KEY_new();
EC_GROUP *ecgroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_set_group(eckey, ecgroup);
EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
int kpGenerationStatus = EC_KEY_generate_key(eckey);
if (kpGenerationStatus) {
return eckey;
}
return nullptr;
}
Given the keypair returned by the function above, I want to export the public key to an ASN1.DER format which can be imported using the java method above.
I convert the public key which is of type EC_POINT* to its hex form using EC_POINT_point2hex() by doing the following:
EC_GROUP *ecgroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY *keypair = generate_keypair();
char *result = NULL;
BN_CTX *ctx;
ctx = BN_CTX_new();
const EC_POINT *pub = EC_KEY_get0_public_key(keypair);
result = EC_POINT_point2hex(ecgroup, pub, POINT_CONVERSION_UNCOMPRESSED, ctx);
printf("%s\n", result);
Which return the following:
04F588CD1D7103A993D47E53D58C3F40BE8F570604CF2EA01A7657C1423EB19C51BC379F0BEE1FAA60BB9A07DE73EA9BEF7709C1C6429D4051B44F73A458FFB80D
When I inspect this with the ASN.1 decoder I see a message which says Length over 48 bits not supported at position 1 and trying to import it using the java method I receive an error as follows:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: DerInputStream.getLength(): Should use short form for length
Is there something I am missing while exporting the public key from EC_POINT* to a X.509 Encoded hex string that I can import for validating any signatures?
You are going in the incorrect direction as you want ASN1 base64 value.
EC_POINT_point2hex is converting the internal public key value to hex. It's not in ASN1 format.
You can produce what you want from the command line like so:
Generate EC private key:
openssl ecparam -name prime256v1 -genkey -noout -out key.pem
Extra public key in DER(ASN1) format:
openssl ec -in key.pem -pubout -outform der -out public.cer
Convert to base64
openssl base64 -in .\public.cer
If you take that output and paste it into ASN.1 decoder link it works fine.
Now to turn this into code, you have the EC key generation, but what you want is the steps to:
Generate ASN1 formatted public key
Convert it to base64
To generate the ASN1 formatted public key you want to use the i2d_EC_PUBKEY set of methods and then convert to base64 using BIO_f_base64 filter.
So here is an example problem that when I copy the output to ASN.1 decoder link it works fine.
#include <openssl/bio.h>
#include <openssl/ec.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
EC_KEY* generate_keypair() {
EC_KEY *eckey = EC_KEY_new();
EC_GROUP *ecgroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_set_group(eckey, ecgroup);
EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
int kpGenerationStatus = EC_KEY_generate_key(eckey);
if (kpGenerationStatus) {
return eckey;
}
return nullptr;
}
int main()
{
EC_GROUP *ecgroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY *keypair = generate_keypair();
BIO* out = BIO_new(BIO_s_mem());
BIO* b64 = BIO_new(BIO_f_base64());
BIO_push(b64, out);
i2d_EC_PUBKEY_bio(b64, keypair);
BIO_flush(b64);
// do what you want this the output in out memory BIO
char* p;
long length = BIO_get_mem_data(out, &p);
// ensure null terminated but copying the buffer into a string to output...
puts(std::string(p, length).c_str());
BIO_free_all(out);
}
I can't complete on the Java side, but if it works with the manual openssl generated base64 string then it will work with the sample application.

Firebase Cloud Messaging + Django: How to securely store the service account's private key?

I just started implementing FCM into my Django backend.
The problem I encountered is the following.
In the docs you are told to generate a private key JSON file and securely store it.
Usually I store my keys in an os.env variable. But this is not possible, since this is a whole file and not just a value. Also on the same page, the doc tells you how to get a request token:
def _get_access_token():
"""Retrieve a valid access token that can be used to authorize requests.
:return: Access token.
"""
credentials = ServiceAccountCredentials.from_json_keyfile_name(
'service-account.json', SCOPES)
access_token_info = credentials.get_access_token()
return access_token_info.access_token
As you can see here, the library needs direct access to the file.
So my question is? How do I securely store this? I'm currently hosting on heroku, so I need this in my version control system.
The way we do it:
Encrypt the json string / Store in Resource file
Decryption key, stored as Env variable.
When creating the credentials decrypt the encrypted string.
this is Based on what ivanspenchev suggested in his post.
Flow is : read json data -> encrypt with a key - > decrypt with a key. I am not using his suggested encrypt algo, because I dont like it.
But the basic idea is, that you have a class "EncryptEngine" that does two things, encrypts a string, and decrypts a string.
public class EncryptEngine
{
Cipher ecipher;
Cipher dcipher;
// 8-byte Salt
byte[] salt = {
(byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32,
(byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03
};
// Iteration count
int iterationCount = 19;
public EncryptEngine() {
}
/**
*
* #param secretKey Key used to encrypt data
* #param plainText Text input to be encrypted
* #return Returns encrypted text
* #throws java.security.NoSuchAlgorithmException
* #throws java.security.spec.InvalidKeySpecException
* #throws javax.crypto.NoSuchPaddingException
* #throws java.security.InvalidKeyException
* #throws java.security.InvalidAlgorithmParameterException
* #throws java.io.UnsupportedEncodingException
* #throws javax.crypto.IllegalBlockSizeException
* #throws javax.crypto.BadPaddingException
*
*/
public String encrypt(String secretKey, String plainText)
throws NoSuchAlgorithmException,
InvalidKeySpecException,
NoSuchPaddingException,
InvalidKeyException,
InvalidAlgorithmParameterException,
UnsupportedEncodingException,
IllegalBlockSizeException,
BadPaddingException {
//Key generation for enc and desc
KeySpec keySpec = new PBEKeySpec(secretKey.toCharArray(), salt, iterationCount);
SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
// Prepare the parameter to the ciphers
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
//Enc process
ecipher = Cipher.getInstance(key.getAlgorithm());
ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
String charSet = "UTF-8";
byte[] in = plainText.getBytes(charSet);
byte[] out = ecipher.doFinal(in);
String encStr = new String(Base64.getEncoder().encode(out));
return encStr;
}
/**
* #param secretKey Key used to decrypt data
* #param encryptedText encrypted text input to decrypt
* #return Returns plain text after decryption
* #throws java.security.NoSuchAlgorithmException
* #throws java.security.spec.InvalidKeySpecException
* #throws javax.crypto.NoSuchPaddingException
* #throws java.security.InvalidKeyException
* #throws java.security.InvalidAlgorithmParameterException
* #throws java.io.UnsupportedEncodingException
* #throws javax.crypto.IllegalBlockSizeException
* #throws javax.crypto.BadPaddingException
*/
public String decrypt(String secretKey, String encryptedText)
throws NoSuchAlgorithmException,
InvalidKeySpecException,
NoSuchPaddingException,
InvalidKeyException,
InvalidAlgorithmParameterException,
UnsupportedEncodingException,
IllegalBlockSizeException,
BadPaddingException,
IOException {
//Key generation for enc and desc
KeySpec keySpec = new PBEKeySpec(secretKey.toCharArray(), salt, iterationCount);
SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
// Prepare the parameter to the ciphers
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
//Decryption process; same key will be used for decr
dcipher = Cipher.getInstance(key.getAlgorithm());
dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
byte[] enc = Base64.getDecoder().decode(encryptedText);
byte[] utf8 = dcipher.doFinal(enc);
String charSet = "UTF-8";
String plainStr = new String(utf8, charSet);
return plainStr;
}
public static void main(String[] args) throws Exception {
EncryptEngine cryptoUtil=new EncryptEngine();
String key="ezeon8547";
JsonFactory f = new JsonFactory();
JsonParser jp =
f.createJsonParser(/MyDocuments/Repo/Myproject/service-account.json);
String plain;
while (jp.nextToken() == JsonToken.START_OBJECT)) {
plain += jp.toString();
}
String enc=cryptoUtil.encrypt(key, plain);
System.out.println("Original text: "+plain);
System.out.println("Encrypted text: "+enc);
String plainAfter=cryptoUtil.decrypt(key, enc);
System.out.println("Original text after decryption: "+plainAfter);
}
}
Most teams keep the JSON file out of version control and add to the environment manually.
While not exactly the same as keeping the values in an environment variable, it has similar security. A developer with only access to version control can't access the key, and a developer cannot accidentally run on their own system with the production keys. But on the other hand: someone who has access to the production server, can get the key in both cases.
please read the below documentation provided for firebase_admin.credentials.Certificate().
So, you can create a credential Certificate by passing a dict from the parsed key file contents. key file content can come from an encrypted environment variable value. Use this credential to initialize the app.

RSA encryption between c++ and node.js

I have to send some encrypted data throught the network (websocket)
I generated a key pair with the the following node.js module :
https://github.com/juliangruber/keypair
My public key looks like this:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAlUiMDQsBgj5P/T86w/eg9MXUj8M4WMVihP8YzmDxMqCFb7D+w4N/1XcxWxQT
....
Wo+SRCsr6npfp1ctDhMtkXIeNT4lKf3qUGhP5tbx/TreaNF/d8zCeinGR/KeBGadMwIDAQAB
-----END RSA PUBLIC KEY-----
In the C++ code, I generated a RSA class with read the public key via a char*
const char rsaKey1[] = "-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAlUiMDQsBgj5P/T86w/eg9MXUj8M4WMVihP8YzmDxMqCFb7D+w4N/1XcxWxQT\n"
....
"Wo+SRCsr6npfp1ctDhMtkXIeNT4lKf3qUGhP5tbx/TreaNF/d8zCeinGR/KeBGadMwIDAQAB\n"
"-----END RSA PUBLIC KEY-----\n";
BIO* bio = BIO_new_mem_buf( rsaKey1, strlen(rsaKey1));
m_rsaPubKey = PEM_read_bio_RSAPublicKey(bio, NULL, NULL, NULL);
usigned the m_rsaPubKey , I have able to generate a std::vector of unsigned char with encrypted data
std::vector<u8> Rsa::encrypt(std::string & msg)
{
std::vector<u8> encryptedData;
char *encrypt = new char[RSA_size(m_rsaPubKey)];
int encryptLen;
if (encryptLen = RSA_public_encrypt(msg.size() + 1, (unsigned
char*)msg.c_str(), (unsigned char*)encrypt, m_rsaPubKey,
RSA_PKCS1_OAEP_PADDING) == -1)
{
LogOutSys("error encoding string");
}
for (u32 i = 0; i < strlen(encrypt); i++)
{
encryptedData.push_back(encrypt[i]);
}
delete encrypt;
return encryptedData;
}
I don't get any errors while reading the public key or encrypting my data so I assume the encryption went ok.
then the data went throught a websocket and is received with node.js
the private key is read like this:
var rsa = new RSA(fs.readFileSync("./rsa-keys/sj_private_1.pem"),
{encryptionScheme :'pkcs8'})
and decoding
var decrypted = rsa.decrypt(data)
where data is a buffer of same length and content (no corruption while sending via the websocket)
c++ side:
encrypted len 256, first bytes 117 125 58 109
node size :
Buffer(256) [117, 125, 58, 109, 38, 229, 7, 189, …]
the rsa.decrypt generated an exception :
TypeError: Cannot read property 'length' of null
I tried several encryptionScheme option (including the default , but always getting the same error or Incorrect key or data
Because of the random padding in OAEP, troubleshooting encryption issues with it can sometimes be a bit tricky.
For further troubleshooting use the following checklist to shoot down potential issues:
Make sure you use the same crypto mechanism on both ends. In your C++ code you are using RSA_PKCS1_OAEP_PADDING but the JavaScript lines in your question does not tell what mechanism you use there.
Make sure that the mechanisms are implemented the same ways in both C++ and Node libraries. It is crucial you have the same hashing method and MGF1 (mask generation function) in both implementations. This is one of the most typical failing points that I've seen in my career.
Since you are working with byte arrays, make sure you are not having any issues in the byte order. In other words, make sure both ends talks the same language in regard of endianness (For self-study: https://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Data/endian.html).

aws s3 - browser upload using post - gwt & java

I am working on a wgt webapp and would like to upload files to s3 from the browser.
Since my credentials are on the server, I need to create the signature on the server and send it to the client to be able to ulpoad.
here is the code I am using:
-dateStamp is in the right format - yyyyMMdd
-policy is base64 encoded - I double checked that
public static String getSignatureForS3Upload(final String dateStamp, final String policy) {
byte[] signingKey = null;
byte[] signature = null;
String strSignature = null;
try {
signingKey = AwsUtill.getSignatureKey(AppConfig.getS3SecretKey(), dateStamp,
AppConfigShared.getMyAwsS3RegionName(), "s3");
signature = HmacSHA256(policy, signingKey);
strSignature = bytesToHex(signature);
}
catch (Exception e) {
// log
}
ServerDBLogger.log(Level.INFO, byteArrayToHex(signature));
ServerDBLogger.log(Level.INFO, bytesToHex(signature));
return strSignature;
private static byte[] HmacSHA256(final String data, final byte[] key) throws Exception {
String algorithm = "HmacSHA256";
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data.getBytes("UTF-8"));
}
private static byte[] getSignatureKey(final String key, final String dateStamp, final String regionName,
final String serviceName) throws Exception {
byte[] kSecret = ("AWS4" + key).getBytes("UTF-8");
byte[] kDate = HmacSHA256(dateStamp, kSecret);
byte[] kRegion = HmacSHA256(regionName, kDate);
byte[] kService = HmacSHA256(serviceName, kRegion);
byte[] kSigning = HmacSHA256("aws4_request", kService);
return kSigning;
}
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(final byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
With the genarated signature I get an error message:
The request signature we calculated does not match the signature you provided. Check your key and signing method.
What am I doing wrong?
Is there any good tutorial or sample code to do this?
Thank you!
There have been changes to the AWS signature process - the version at this time (May 2016) is AWS Signature version 4 (http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). You need to be careful when looking at examples and Stackoverflow etc that the information relates to the same signature version you are using as they don't mix well.
I started using the AWS SDK for an angular/node file upload but eventually found it easier to generate the policy on the server (node.js) side without the SDK. There is a good example (albeit node based which may not be what you are looking for) here: https://github.com/danialfarid/ng-file-upload/wiki/Direct-S3-upload-and-Node-signing-example (but note the issue with the S3 bucket name here: AngularJs Image upload to S3 ).
One key thing to watch is that you correctly include the file content type in the policy generation and that this content type properly matches the content type of the file you are actually uploading.