Extracting public key information from a PEM certificate - android

This is the first time I am doing this SSL pinning on Android.
When creating OkHttp, I am adding this code:
certificatePinner(
CertificatePinner.Builder().add(
"url of the server",
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
).build()
)
the second parameter expects this:
SHA-256 or SHA-1 hashes. Each pin is a hash of a certificate's Subject Public Key Info, base64-encoded and prefixed with either sha256/ or sha1/.
I was given a certificate in a txt file that starts with --BEGIN CERTIFICATE-- and ends with --END CERTIFICATE--.
I am struggling to extract the hash of Public Key Info and convert it to base64.
So far I have tried these methods:
Method 1:
I put the certificate contents without BEGIN CERTIFICATE and END CERTIFICATE into some string variable. Tried to convert it into X509Certificate.
private fun certificateFromString(base64: String): X509Certificate? {
val decoded = Base64.decode(base64, Base64.NO_WRAP)
val inputStream = ByteArrayInputStream(decoded)
return CertificateFactory.getInstance("X.509").generateCertificate(inputStream) as? X509Certificate
}
Then I pass this certificate here to get Sha256 hash of the public key. Also, pay attention to c.encoded and c.publicKey.encoded. I am not sure if the method works correctly.
private fun getFingerprint(c: X509Certificate?): String {
var certificate = ""
try {
val md = MessageDigest.getInstance("SHA-256")
var publicKey = ByteArray(0)
if (c != null) {
publicKey = md.digest(c.encoded) // I tried both
publicKey = md.digest(c.publicKey.encoded) // I tried both
}
val hexString = StringBuilder()
for (aPublicKeyByte in publicKey) {
val appendString = Integer.toHexString(0xFF and aPublicKeyByte.toInt())
if (appendString.length == 1) hexString.append("0")
hexString.append(appendString)
}
certificate = hexString.toString()
} catch (e1: NoSuchAlgorithmException) {
e1.printStackTrace()
} catch (e1: CertificateEncodingException) {
e1.printStackTrace()
}
return certificate
}
then I am converting that string result to base64 like this:
private fun base64(openKey: String): String {
return Base64.encodeToString(openKey.toByteArray(), Base64.NO_WRAP).toString()
}
then I add the resulting String into the CertificatePinner class as sha256/resultingStringInBase64.
Method 2:
I changed the .txt into .pem in order to use openssl command line tools.
openssl rsa -in myCert.pem -pubout> myCert.pub
it returned writing RSA key
and when I open the generated myCert.pub, I see a text with ---BEGUN PUBLIC KEY--- and ---END PUBLIC KEY--- and a long list of letters between them.
Then I ran this to extract the sha256 hash:
openssl rsa -in myCert.pub -pubin -outform der | openssl dgst -sha256
this gave me a 64 character string 2c180286549...b1ba7.
Then I ran the command again but added base64 conversion.
openssl rsa -in myCert.pub -pubin -outform der | openssl dgst -sha256 | openssl enc -base64
The result is completely different than Method 1. Should it be the same? If so, could someone point me in the right direction? Or show how to properly get the SHA256 of Public Key.

My method I just used recently and worked succesfully:
In your Terminal, where the folder containing public key file is located, write the command:
openssl x509 -in yourFile.pem -pubkey -noout | open ssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
The result will be the your desired string (encoded in base64, which means that it'll have 44 characters, including an equal (=) symbol at the end), and must be placed in your Android code after sha256/....
Also, several methods could generate different hashes, so you can have multiple valid strings, meaning that both your generated hashes could be correct.

Related

SSL Cert issued by Windows CA, exported by OpenSSL works on Windows but not Android

I'm setting up a pipeline for an android device to request a certificate like so:
var subjectName = $"CN={GetFQDN()},O=My Org,OU=Test,T=Test,ST=OK,C=US";
// Create new Object for Issuer and Subject
var subject = new X509Name(subjectName);
// Generate the key Value Pair, which in our case is a public Key
var random = new SecureRandom();
const int strength = 2048;
var keyGenerationParameters = new KeyGenerationParameters(random, strength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
AsymmetricCipherKeyPair subjectKeyPair = keyPairGenerator.GenerateKeyPair();
//PKCS #10 Certificate Signing Request
Pkcs10CertificationRequest csr = new Pkcs10CertificationRequest("SHA1WITHRSA", subject, subjectKeyPair.Public, null, subjectKeyPair.Private);
//Convert BouncyCastle CSR to .PEM file.
StringBuilder CSRPem = new StringBuilder();
PemWriter CSRPemWriter = new PemWriter(new StringWriter(CSRPem));
CSRPemWriter.WriteObject(csr);
CSRPemWriter.Writer.Flush();
//get CSR text
var CSRtext = CSRPem.ToString();
// Write content into a Txt file
using (StreamWriter f = new StreamWriter(Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), #"MyCsr.txt"), false))
{
f.Write(CSRtext);
}
CSRPem = new StringBuilder();
CSRPemWriter = new PemWriter(new StringWriter(CSRPem));
CSRPemWriter.WriteObject(subjectKeyPair.Private);
CSRPemWriter.Writer.Flush();
CSRtext = CSRPem.ToString();
using (StreamWriter f = new StreamWriter(Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), #"PrivateKey.pem"), false))
{
f.Write(CSRtext);
}
The csr is sent to a Windows CA server, then the resulting .cer is combined with the private key using the following openssl command (on Windows 11):
openssl pkcs12 -export -in myCer.cer -inkey myKey.pem -out myPfx.pfx
Regardless of what the password is set to, including blank, the certificate will install correctly on any Windows PC but says incorrect password on any Android device, when installing through the system dialogue. When trying to decode the .pfx programmatically, like:
Application.Context.Assets.Open("newCert.pfx").CopyTo(mmstream);
byte[] b = mmstream.ToArray();
var myCert = new X509Certificate2(b, String.Empty);
I get the following error:
System.Security.Cryptography.CryptographicException: Unable to decode certificate. ---> System.Security.Cryptography.CryptographicException: `MonoBtlsPkcs12.Import` failed.
It seems like Android may be using a different algorithm to encrypt using the password by default? But every post I've found about exporting to Android from openssl just uses the default arguments.
Looks like Android accepts certs I generate with 32 bit openssl and not 64 bit. Unsure why this is.
openssl pkcs12 -export -in myCer.cer -inkey myKey.pem -out myPfx.pfx
worked fine in 32 bit openssl

Save data on KeyStore and retrieve it using keychain

I want to save some of my sensitive data (string) in keyStore. I found that keyStore only accepts secretKey objects. But, I'm not able to store it and and retreive it later using keyChain callback by using the alias name of the secretKey
Any help will be appreciated..!
I think you are looking something like MD5. An MD5 hash is created by taking a string of an any length and encoding it into a 128-bit fingerprint. Encoding the same string using the MD5 algorithm will always result in the same 128-bit hash output. MD5 hashes are commonly used with smaller strings when storing passwords, credit card numbers or other sensitive data in databases such as the popular MySQL. This tool provides a quick and easy way to encode an MD5 hash from a simple string of up to 256 characters in length.
MD5 hashes are also used to ensure the data integrity of files. Because the MD5 hash algorithm always produces the same output for the same given input, users can compare a hash of the source file with a newly created hash of the destination file to check that it is intact and unmodified.
Hashing String with MD5:
public class JavaMD5Hash {
public static void main(String[] args) {
String password = "MyPassword123";
System.out.println("MD5 in hex: " + md5(password));
System.out.println("MD5 in hex: " + md5(null));
//= d41d8cd98f00b204e9800998ecf8427e
System.out.println("MD5 in hex: "
+ md5("The quick brown fox jumps over the lazy dog"));
//= 9e107d9d372bb6826bd81d3542a419d6
}
public static String md5(String input) {
String md5 = null;
if(null == input) return null;
try {
//Create MessageDigest object for MD5
MessageDigest digest = MessageDigest.getInstance("MD5");
//Update input string in message digest
digest.update(input.getBytes(), 0, input.length());
//Converts message digest value in base 16 (hex)
md5 = new BigInteger(1, digest.digest()).toString(16);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return md5;
}
}
referance :
http://viralpatel.net/blogs/java-md5-hashing-salting-password/
https://www.mkyong.com/java/java-md5-hashing-example/
http://www.asjava.com/core-java/java-md5-example/
you can use shared preferance which is very easy to handle also.
https://developer.android.com/training/basics/data-storage/shared-preferences.html
Android Shared preferences example

how to get Key_name for fingerprint scanner?

Fingerprint API preview for Android N is found here with Sample App Sample App .As of this writing, createKey() in this method specify key_name,i don't know about key_name,Please anyone tell about key_name
/** Alias for our key in the Android Key Store */
private static final String KEY_NAME = "my_key";
public void createKey() {
// The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint
// for your flow. Use of keys is necessary if you need to know if the set of
// enrolled fingerprints has changed.
try {
mKeyStore.load(null);
// Set the alias of the entry in Android KeyStore where the key will appear
// and the constrains (purposes) in the constructor of the Builder
mKeyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
// Require the user to authenticate with a fingerprint to authorize every use
// of the key
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
mKeyGenerator.generateKey();
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
| CertificateException | IOException e) {
throw new RuntimeException(e);
}
}
this my part of code,here what is KEY_NAME and key_name get from where
Just run this cmd on your terminal
/home/nn1android01user/Documents/Keystor_alicante/gerber_keystore.jks ->
this is your keystore file path
gerber -> alias name
keytool -exportcert -alias gerber -keystore /home/nn1android01user/Documents/Keystor_alicante/gerber_keystore.jks | openssl sha1 -binary | openssl base64

No peer certificate Exception - Volley and Android with self signed certificate

I'm trying to make my app communicate with my server via https.
As I don't want to pay to get my server certificate signed by a trusted CA, the solution is to use a self signed certificate.
So, I've created my caconfig.cnf as follows:
[ ca ]
default_ca = CA_default # The default ca section
[ CA_default ]
dir = ./demoCA # top dir
database = $dir/index.txt # index file.
new_certs_dir = $dir/newcerts # new certs dir
certificate = $dir/cacert.pem # The CA cert
serial = $dir/serial # serial no file
private_key = $dir/private/cakey.pem # CA private key
RANDFILE = $dir/private/.rand # random number file
default_days = 365 # how long to certify for
default_crl_days = 30 # how long before next CRL
default_md = md5 # md to use
policy = policy_any # default policy
email_in_dn = no # Don't add the email into cert DN
name_opt = ca_default # Subject name display option
cert_opt = ca_default # Certificate display option
copy_extensions = none # Don't copy extensions from request
[ policy_any ]
countryName = optional
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
Then, I've created and signed my certificate using the following commands:
$ mkdir myCA myCA/private myCA/newcerts
$ echo "01" > myCA/serial
$ touch demoCA/index.txt
$ openssl genrsa -des3 -out myCA/private/cakey.pem 1024
$ openssl req -new -x509 -days 3650 -key myCA/private/cakey.pem -out myCA/cacert.pem
$ openssl req -sha1 -newkey rsa:2048 -keyout server-key.pem -out server-cert-req.pem -subj '/CN=myhost/' -nodes
$ openssl ca -config caconfig.cnf -in server-cert-req.pem -out server-cert.pem
$ openssl x509 -inform PEM -in cacert.pem -outform DER -out certificate.cer
$ rm server-cert-req.pem
In my nodejs server code, I'm creating the https server like this:
var express = require('express');
var https = require('https');
var PORT = 443;
var app = express();
app.get('/', function (req, res) {
res.send("Server is working");
});
var httpsOptions = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem')
};
https.createServer(httpsOptions, app).listen(PORT, function() {
console.log('%s: Node server started on port %d ...', Date(Date.now() ), PORT);
});
In order to test if everything is correct, I've also created a node client script, which makes a request to my server. Here is the code for my node client:
var https = require('https');
var fs = require('fs');
var request = https.request({
host: 'myhost',
port: 443,
path: '/',
method: 'GET',
rejectUnauthorized: true,
// Once it is self signed, I'm using my server certificate (public key).
ca: [fs.readFileSync('cacert.pem').toString()]
}, function(response) {
response.on('data', function(data) {
console.log(data.toString());
});
});
request.on('error', function(err) {
console.log(err);
})
request.end();
When I run my node client script, it works perfectly. On the other hand, the Android Volley Examples app, which I'm using to check how Volley works with https, is not working. Below, I'm describing all the steps I've followed in order to try to make it work.
I've created a .bks file using my certificate.cer file using the following command:
keytool -importcert -v -trustcacerts -file "certificate.cer" -alias IntermediateCA -keystore "res/raw/my_keystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-146.jar" -storetype BKS -storepass mysecret
Then, I've verified if the certificate was imported correctly into the .bks as follows:
keytool -list -keystore "res/raw/my_keystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-146.jar" -storetype BKS -storepass mysecret
And I got the following output, that means it's correct:
Keystore type: BKS
Keystore provider: BC
Your keystore contains 1 entry
imeto_alias, Oct 16, 2014, trustedCertEntry,
Certificate fingerprint (SHA1): 03:DC:A1:6A:9B:1D:AD:59:A9:9B:1F:C2:43:7E:80:07:3B:B6:BE:CB
I've came across to this tutorial, and, as I'm using Volley, I've decided to follow it. So, below are the following changes I've made to the example project.
Got Volley from git clone https://android.googlesource.com/platform/frameworks/volley
Got Android Volley Examples project from git clone git://github.com/ogrebgr/android_volley_examples.git
Copied my_keystore.bks containing the self-signed public key in res/raw;
Opened Act_SsSslHttpClient in the examples project, found "R.raw.test" and replaced it with R.raw.my_keystore;
Found "new SslHttpClient(" and replaced the default password "test123″ with "mysecret";
Replaced "44400" with the HTTPS port of my server/virtualhost ("443"). (I could also remove this parameter since "443" is the default port;
Replaced "https://tp.bolyartech.com:44400/https_test.html" with my server URL.
Started the app, went to "HTTPS with self-signed cert", then "Execute HTTPS request"
But, when I pressed the button, I got the following exception:
javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
Below, is my JavaCode...
public class Act_SsSslHttpClient extends Activity {
private TextView mTvResult;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act__ss_ssl_http_client);
mTvResult = (TextView) findViewById(R.id.tv_result);
Button btnSimpleRequest = (Button) findViewById(R.id.btn_simple_request);
btnSimpleRequest.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// Replace R.raw.test with your keystore
InputStream keyStore = getResources().openRawResource(R.raw.my_keystore);
// Usually getting the request queue shall be in singleton like in {#seeAct_SimpleRequest}
// Current approach is used just for brevity
RequestQueue queue = Volley.newRequestQueue(Act_SsSslHttpClient.this,
new ExtHttpClientStack(new SslHttpClient(keyStore,
"mysecret")));
StringRequest myReq = new StringRequest(Method.GET,
"https://myServerURL/",
createMyReqSuccessListener(),
createMyReqErrorListener());
queue.add(myReq);
}
});
}
...
}
Does anybody know the solution?
Thank you.
I am using self signed certificate in my test environment. To make it work I call this method in my Application class in onCreate method. It makes all certificates accepted. It is not save, but for test purposes is ok.
#Override
public void onCreate() {
nuke();
super.onCreate();
}
public static void nuke() {
try {
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] myTrustedAnchors = new X509Certificate[0];
return myTrustedAnchors;
}
#Override
public void checkClientTrusted(X509Certificate[] certs,
String authType) {}
#Override
public void checkServerTrusted(X509Certificate[] certs,
String authType) {}
} };
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection
.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection
.setDefaultHostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
} catch (Exception e) {}
}

How to verify a public key in string/byte[] format against certificates in bks store

I want to verify if a certificate with a certain public key exits in android keystore or not.I have key which is holded in a string.How do I verify if the corresponding certificate is present in keystore.
When checked the apis i found certificate.verify(mykey); is the only option.but mykey sholud be of type PublicKey and I have it in string.
String is 0r1wxn7wIXJuS/hDnDvectD2VTmel9akk8awIWAXIRo= .....this is a hash of public key..i have to return back the certficate corresponding to this key.
Can anyone help me ?
Can anyone help me ?
Regards
kozlov
KeyStore store = ... ;
byte[] target = ... ; // Base-64 decode your string.
MessageDigest digest = MessageDigest.getInstance(algorithm);
Enumeration<String> aliases = store.aliases();
while(aliases.hasMoreElements()) {
String alias = aliases.nextElement();
Certificate c = store.getCertificate(alias);
if (c == null)
continue;
PublicKey pub = c.getPublicKey();
byte[] hash = digest.digest(pub.getEncoded());
if (MessageDigest.isEqual(hash, target)) {
// Certificate "c" is a match.
}
}

Categories

Resources