How to implement google play security verify purchase server side? - java

I want to implement in-app purchase verification server-side in PHP language.
I tested this link but returned false for correct data set.
The java function:
public class Security {
private static final String TAG = "IABUtil/Security";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) {
Log.e(TAG, "Purchase verification failed: missing data.");
return false;
}
PublicKey key = Security.generatePublicKey(base64PublicKey);
return Security.verify(key, signedData, signature);
}
public static PublicKey generatePublicKey(String encodedPublicKey) {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
Log.e(TAG, "Invalid key specification.");
throw new IllegalArgumentException(e);
} catch (Base64DecoderException e) {
Log.e(TAG, "Base64 decoding failed.");
throw new IllegalArgumentException(e);
}
}
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
...
}
}
I tried this code:
function verify_market_in_app($signed_data, $signature, $public_key_base64)
{
$key = "-----BEGIN PUBLIC KEY-----\n".
chunk_split($public_key_base64, 64,"\n").
'-----END PUBLIC KEY-----';
//using PHP to create an RSA key
$key = openssl_get_publickey($key);
//$signature should be in binary format, but it comes as BASE64.
//So, I'll convert it.
$signature = base64_decode($signature);
//using PHP's native support to verify the signature
$result = openssl_verify(
$signed_data,
$signature,
$key,
OPENSSL_ALGO_SHA1);
if (0 === $result)
{
return false;
}
else if (1 !== $result)
{
return false;
}
else
{
return true;
}
}
but this isn't work correctly.
I use openssl_error_string() function and get this error:
error:0906D064:PEM routines:PEM_read_bio:bad base64 decode
Can any body help?

Related

AES with salt from java to PHP get different result

I had issue when converting AES encryption from java to php, it show different value result in php, that should show the exact value from java
Java
public class AesUtil {
private final Cipher cipher;
public final static String passPhrase = "";
public final static String IV = ""; //32bit IV Lenght
public final static String SALT = "";
public final static int KEY_SIZE = 128;
public final static int ITERATION_COUNT = 10000;
public AesUtil() {
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
}
catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw fail(e);
}
}
//Call this function to encrypt some String
public String encrypt(String passPhrase, String plaintext) {
try {
SecretKey key = generateKey(SALT, passPhrase);
byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, IV, plaintext.getBytes("UTF-8"));
return base64(encrypted);
}
catch (UnsupportedEncodingException e) {
throw fail(e);
}
}
private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) {
try {
cipher.init(encryptMode, key, new IvParameterSpec(hex(IV)));
return cipher.doFinal(bytes);
}
catch (InvalidKeyException
| InvalidAlgorithmParameterException
| IllegalBlockSizeException
| BadPaddingException e) {
throw fail(e);
}
}
private SecretKey generateKey(String salt, String passphrase) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), hex(salt), ITERATION_COUNT, KEY_SIZE);
SecretKey key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return key;
}
catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw fail(e);
}
}
public static String random(int length) {
byte[] salt = new byte[length];
new SecureRandom().nextBytes(salt);
return hex(salt);
}
public static String base64(byte[] bytes) {
return new String(Base64.encodeBase64(bytes));
}
public static byte[] base64(String str) {
return Base64.decodeBase64(str.getBytes());
}
public static String hex(byte[] bytes) {
return Hex.encodeHexString(bytes);
}
public static byte[] hex(String str) {
try {
return Hex.decodeHex(str.toCharArray());
}
catch (DecoderException e) {
throw new IllegalStateException(e);
}
}
private IllegalStateException fail(Exception e) {
return new IllegalStateException(e);
}
}
this is my code in php version,
<?php
$password = "";
$salt = "";
$iv = ""; //32bit IV Lenght
$length = 16;
$salt = "";
$interation = 10000;
$key1 = mb_convert_encoding($password, "UTF-8");
$bytes = openssl_pbkdf2($key1, $salt, $length, $interation, "sha1");
$bytes2 = hash_pbkdf2("sha1", $password, $salt, $interation, $length, true);
$ciphertext_b64 = base64_encode(openssl_encrypt($plaintext,"aes-128-cbc", $bytes2,OPENSSL_RAW_DATA, $iv));
?>
Please tell me how to solve this problem, if you have any suggestion in different programming language it's no problem, thank you

Error trying to verify signature on CBOR message

I'm having problems decoding EU Digital Certificate ("Covid pass") using COSE-JAVA. Public key appears to load ok but when I try to validate the CBOR message, I get following error:
COSE.CoseException: Signature verification failure
at COSE.SignCommon.validateSignature(SignCommon.java:205)
at COSE.Signer.validate(Signer.java:212)
at COSE.Message.validate(Message.java:288)
Caused by: java.lang.NullPointerException: Attempt to get length of null array
at COSE.SignCommon.convertConcatToDer(SignCommon.java:212)
Here is code for validation:
public static String DecodeMessage(byte[] data) throws CoseException, CborParseException {
Message m = Encrypt0Message.DecodeFromBytes(data);
PublicKey key = getPublicKey("here goes PEM of public key");
CborMap cborMap = CborMap.createFromCborByteArray(m.GetContent());
CounterSign signer = new CounterSign();
signer.setKey(new OneKey(key, null));
signer.addAttribute(HeaderKeys.Algorithm, AlgorithmID.ECDSA_256.AsCBOR(), Attribute.ProtectedAttributes);
// error happens here
m.validate(signer);
return cborMap.toJsonString();
}
This is how public key is generated from PEM:
public static PublicKey getPublicKey(String keyData) {
try
{
Security.removeProvider("BC");
Security.addProvider(new BouncyCastleProvider());
Reader rdr = new StringReader(
"-----BEGIN EC PUBLIC KEY-----\n" + keyData + "\n" + "-----END EC PUBLIC KEY-----\n"
);
org.bouncycastle.util.io.pem.PemObject spki = new org.bouncycastle.util.io.pem.PemReader(rdr).readPemObject();
byte[] content = spki.getContent();
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(content);
return KeyFactory.getInstance("EC", "BC").generatePublic(pubKeySpec);
}
catch (Exception ex)
{
return null;
}
Public key is defined as:
"publicKeyAlgorithm": {
"hash": {
"name": "SHA-256"
},
"name": "ECDSA",
"namedCurve": "P-256"
},
"publicKeyPem": "..."
Here is code for doing same thing in Python, might help someone. Code must work on Android 7.1 (embedded device, no way to upgrade to newer Android).
Solved...
public static boolean VerifySignature(byte[] data, String publicKey) {
boolean result = false;
try {
Message m = Encrypt0Message.DecodeFromBytes(data);
Sign1Message sm = (Sign1Message) m;
PublicKey key = getPublicKey(publicKey);
if (sm.validate(new OneKey(key, null))) {
result = true;
}
} catch (Exception ex) {
log.error("Error verifying signature", ex);
}
return result;
}
and
public static PublicKey getPublicKey(String keyData) {
try {
Security.removeProvider("BC");
Security.addProvider(new BouncyCastleProvider());
Reader rdr = new StringReader(
"-----BEGIN EC PUBLIC KEY-----\n" + keyData + "\n" + "-----END EC PUBLIC KEY-----\n"
);
org.bouncycastle.util.io.pem.PemObject spki = new org.bouncycastle.util.io.pem.PemReader(rdr).readPemObject();
byte[] content = spki.getContent();
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(content);
return KeyFactory.getInstance("EC", "BC").generatePublic(pubKeySpec);
} catch (Exception ex) {
return null;
}
}
ODqaG8mnbro=": {
"serialNumber": "5dfefffd3a560d58",
"subject": "C=BE, O=eHealth - Belgium, CN=Belgium Covid19 DSC 01",
"issuer": "C=BE, O=eHealth - Belgium, CN=Belgium Covid19 Country Signing CA 01",
"notBefore": "2021-05-27T10:12:47.000Z",
"notAfter": "2023-05-27T10:12:47.000Z",
"signatureAlgorithm": "ECDSA",
"fingerprint": "2942d10907cf19f27aeb0dc391f35197c69dea87",
"publicKeyAlgorithm": {
"hash": {
"name": "SHA-256"
},
"name": "ECDSA",
"namedCurve": "P-256"
},
"publicKeyPem": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEU/f/KsmP3NasU/jZo7aulTrd9GHoznfnwWvX8xmHtK49EoobMAG7LhXnpLQ+aRwmmnSMcIWy8wPxM8QDMBUtyA=="
}

Encrypt/Decrypt String using Public/Private Key in Java and Javascript

I am generating a public and private key pair in javascript. The public key is being sent to the server where I have java code that encrypts the string. When I try to decrypt the string in Javascript using a private key I get an error. Here is the Javascript code I used.
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class CryptographyService {
private publicKey: string;
private privateKey: string;
public randomKey: string;
private keyPair: CryptoKeyPair;
public exportedKey: string;
constructor() {
/**Generate the public and private key */
this.generateKeys()
.then(() => {
return this.exportKey(this.keyPair.publicKey);
})
.then(() => {});
}
public setPublicKey(key: string) {
this.publicKey = key;
}
public setRandomKey(key: string) {
this.randomKey = key;
}
public setPrivateKey(key: string) {
this.privateKey = key;
}
public async encryptMessage(text: String) {
const { default: NodeRSA } = await import('node-rsa');
const key = NodeRSA(this.publicKey, 'pkcs8-public');
return key.encrypt(text, 'base64');
}
// For subscription key encryption and decryption
private async generateKeys() {
const keyPair = await crypto.subtle.generateKey(
{
name: 'RSA-OAEP', //algorithm
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256'
},
true,
['encrypt', 'decrypt']
);
this.keyPair = keyPair; /**{
publicKey:
privateKey:
} */
}
private async exportKey(key: CryptoKey) {
/** exportKey() it takes as input a CryptoKey object and gives you the key in an external, portable format.
* crypto.subtle.exportKey(format, key); : returns a promise
* spki format is used to import/export RSA or Elliptic Curve public keys.
*/
const exported = await crypto.subtle.exportKey('spki', key); //Returns an ArrayBuffer
const exportedAsString = this.ab2str(exported); // Converts ArrayBuffer to String
/**btoa encodes a string to base64 */
const exportedAsBase64 = window.btoa(exportedAsString);
this.exportedKey = exportedAsBase64;
}
// Uses private key to decrypt message sent from the backend
public async decryptMessage(input: string) {
const ciphertext = this.str2ab(input);
const decrypted = await window.crypto.subtle.decrypt(
{
name: 'RSA-OAEP'
},
this.keyPair.privateKey,
ciphertext
);
const dec = new TextDecoder();
const decodedMessage = dec.decode(decrypted);
return decodedMessage;
}
private ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
private str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
}
The java code to convert the string to PublicKey Object
String pubKey = loginRequest.publicKey;
PublicKey pk = null;
try {
byte[] keyBytes = Base64.decodeBase64(pubKey);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
pk = kf.generatePublic(spec);
} catch (Exception e) {
System.out.println("Exception in generating primary key: " + e.getMessage());
}
Encrypting the string with the publicKey
public static byte[] encrypt(String text, PublicKey key) {
byte[] cipherText = null;
try {
// get an RSA cipher object and print the provider
final Cipher cipher = Cipher.getInstance(ALGORITHM);
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), PSource.PSpecified.DEFAULT);
// encrypt the plain text using the public key
cipher.init(Cipher.ENCRYPT_MODE, key, oaepParams);
cipherText = cipher.doFinal(text.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
return cipherText;
}
I get back an encrypted string when when I try to decode it in Javascript I get an error. Any idea what I need to change in my code to fix this?

Unable to store secp256k1 generated private key using Amazoun CloudHSM Java Library

When I try to import a secp256k1 private key into my CloudHSM instance, I get the error "java.security.InvalidKeyException: The key is an instance of CaviumKey and cannot be imported." Importing a secp256r1 private key works fine.
I'm using the provided examples as guidance (https://github.com/aws-samples/aws-cloudhsm-jce-examples ) and it seems that the exportKey method doesn't convert the key to a privateKey, but returns a CaviumKey instead (I've linked to the line in the method below).
https://github.com/aws-samples/aws-cloudhsm-jce-examples/blob/master/src/main/java/com/amazonaws/cloudhsm/examples/KeyUtilitiesRunner.java#L278
/**
* Export an existing persisted key.
* #param handle The key handle in the HSM.
* #return Key object
*/
private static Key exportKey(long handle) {
try {
byte[] keyAttribute = Util.getKeyAttributes(handle);
CaviumKeyAttributes cka = new CaviumKeyAttributes(keyAttribute);
System.out.println(cka.isExtractable());
byte[] encoded = Util.exportKey( handle);
if(cka.getKeyType() == CaviumKeyAttributes.KEY_TYPE_AES) {
Key aesKey = new SecretKeySpec(encoded, 0, encoded.length, "AES");
return aesKey;
}
else if(cka.getKeyType() == CaviumKeyAttributes.KEY_TYPE_RSA && cka.getKeyClass() == CaviumKeyAttributes.CLASS_PRIVATE_KEY) {
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(encoded));
return privateKey;
}
else if(cka.getKeyType() == CaviumKeyAttributes.KEY_TYPE_RSA && cka.getKeyClass() == CaviumKeyAttributes.CLASS_PUBLIC_KEY) {
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(encoded));
return publicKey;
} else if(cka.getKeyType() == CaviumKeyAttributes.KEY_TYPE_EC && cka.getKeyClass() == CaviumKeyAttributes.CLASS_PRIVATE_KEY) {
PrivateKey privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(encoded));
return privateKey;
}
else if(cka.getKeyType() == CaviumKeyAttributes.KEY_TYPE_EC && cka.getKeyClass() == CaviumKeyAttributes.CLASS_PUBLIC_KEY) {
PublicKey publicKey = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(encoded));
return publicKey;
}
} catch (BadPaddingException | CFM2Exception e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
This function returns a PrivateKey that is still an instance of CaviumKey, which throws an error when trying to import into the HSM via the Cavium libraries.
Does anyone have an idea of why this is happening or how I could fix this?
Instead of EC, did you tried with CaviumKeyAttributes.KEY_TYPE_ECDSA ?

DSA signature - java.security.SignatureException: invalid encoding for signature

I have problem with DSA signature. My DSA code:
public byte[] signing(String text, PrivateKey privatekey) throws Exception {
byte[] textByte = text.getBytes();
Signature sign = Signature.getInstance("DSA");
sign.initSign(privatekey);
sign.update(textByte);
return sign.sign();
}
public boolean verify(String text, byte[] signature, PublicKey publickey) throws Exception {
byte[] textByte = text.getBytes();
Signature sign = Signature.getInstance("DSA");
sign.initVerify(publickey);
sign.update(textByte);
return sign.verify(signature);
}
And I run it on button action:
private void jButton9ActionPerformed(java.awt.event.ActionEvent evt) {
String message = jTextArea1.getText();
try {
signed = dsa.signing(message, privKey);
} catch (Exception ex) {
Logger.getLogger(DSAFrame.class.getName()).log(Level.SEVERE, null, ex);
}
String signedString = new String(signed);
jTextArea2.setText(signedString);
}
private void jButton10ActionPerformed(java.awt.event.ActionEvent evt) {
String signedString = jTextArea1.getText();
signToVer = jTextArea2.getText().getBytes();
try {
boolean correct = dsa.verify(signedString, signToVer, pubKey);
if(correct == true) {
JOptionPane.showMessageDialog(this, "Podpis poprawny", "Sukces", JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(this, "Podpis niepoprawny", "Błąd", JOptionPane.ERROR_MESSAGE);
}
} catch (Exception ex) {
Logger.getLogger(DSAFrame.class.getName()).log(Level.SEVERE, null, ex);
}
}
When I want verify signature, it throw me an java.security.SignatureException: invalid encoding for signature. I think, it's a problem with conversion string to byte array and back. But I don't know how to make it work.

Categories