I am a newbie with Security in Android ..
I am trying to get the sha1 of the signing certificate fingerprint of the android app ,
I wanna get the same result of the cmd :
keytool -v -list -keystore " PATH of key " -alias "alias of the key" -storpass " password"
I tried this code but it gave me a different result
android.content.pm.Signature[] sigs;
try {
sigs = this.getPackageManager().getPackageInfo(this.getPackageName(),
PackageManager.GET_SIGNATURES).signatures;
byte[] cert = sigs[0].toByteArray();
InputStream input
= new ByteArrayInputStream(cert);
CertificateFactory cf = null;
try {
cf = CertificateFactory.getInstance("X509");
} catch (CertificateException e) {
e.printStackTrace();
}
X509Certificate c = null;
try {
c = (X509Certificate) cf.generateCertificate(input);
Signature signature=null;
signature = Signature.getInstance("SHA1withRSA");
signature.initVerify(c.getPublicKey());
signature.update(cert);
System.out.println("signature"+ signature.sign());
My SignatureUtils class uses SHA-256 (available via the Java 7 keytool), and the values line up. Hence, this method should give you the SHA-1 signature hash:
public static String getSignatureHash(Context ctxt, String packageName)
throws NameNotFoundException,
NoSuchAlgorithmException {
MessageDigest md=MessageDigest.getInstance("SHA-1");
Signature sig=
ctxt.getPackageManager()
.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures[0];
return(toHexStringWithColons(md.digest(sig.toByteArray())));
}
where toHexStringWithColons() is based off of this StackOverflow answer:
public static String toHexStringWithColons(byte[] bytes) {
char[] hexArray=
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
'C', 'D', 'E', 'F' };
char[] hexChars=new char[(bytes.length * 3) - 1];
int v;
for (int j=0; j < bytes.length; j++) {
v=bytes[j] & 0xFF;
hexChars[j * 3]=hexArray[v / 16];
hexChars[j * 3 + 1]=hexArray[v % 16];
if (j < bytes.length - 1) {
hexChars[j * 3 + 2]=':';
}
}
return new String(hexChars);
}
Since SHA-1 is not a great hash algorithm anymore, and since you can get SHA-256 from keytool, you might consider just using my CWAC-Security library and SignatureUtils directly.
Related
I am using SafetyNet to verify the integrity of the android app.
This is the flow as of now.
I generate a nonce value in the server and send it to the SafetyNet service to get the response.
I get the response from the server. Now I want to verify the result on the server.
I get a base64 string. I decode it and get the response as below.
{
"evaluationType": "BASIC",
"ctsProfileMatch": false,
"apkPackageName": "com.test.safetynetproject",
"apkDigestSha256": "CbU9JzwRzQneYqnEXewB56ZzPm1DgQ4LGUK0eGlWmyM=",
"nonce": "U2FnYXI=",
"apkCertificateDigestSha256": [
"AJRBzWCfJIY7QD2cp4sv9t0cCGMRGdxuID9VdPLV1H4="
],
"timestampMs": 1624099377557,
"basicIntegrity": false
}
Now i want to verify the apkCertificateDigestSha256. The sha256 created from my system using cmd is -
C:\Program Files\Java\jdk-11.0.11\bin>keytool -list -v -alias androiddebugkey -keystore C:\Users\.android\debug.keystore
Enter keystore password:
Alias name: androiddebugkey
Creation date: May 25, 2021
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: C=US, O=Android, CN=Android Debug
Issuer: C=US, O=Android, CN=Android Debug
Serial number: 1
Valid from: Tue May 25 11:48:00 IST 2021 until: Thu May 18 11:48:00 IST 2051
Certificate fingerprints:
SHA1: 43:16:E2:63:DB:2A:53:7C:7D:BB:E9:80:7B:05:1C:74:7C:84:66:A2
SHA256: 00:94:41:CD:60:9F:24:86:3B:40:3D:9C:A7:8B:2F:F6:DD:1C:08:63:11:19:DC:6E:20:3F:55:74:F2:D5:D4:7E
Signature algorithm name: SHA1withRSA (weak)
Subject Public Key Algorithm: 2048-bit RSA key
Version: 1
Warning:
The certificate uses the SHA1withRSA signature algorithm which is considered a security risk. This algorithm will be disabled in a future update.
The SHA256
00:94:41:CD:60:9F:24:86:3B:40:3D:9C:A7:8B:2F:F6:DD:1C:08:63:11:19:DC:6E:20:3F:55:74:F2:D5:D4:7E
Question -
I want to verify if the apkCertificateDigestSha256 is the same as the app certificate. Bt unable to find any way to do it.
Tries-
I tried to base64 decode the AJRBzWCfJIY7QD2cp4sv9t0cCGMRGdxuID9VdPLV1H4= and got a random byte array that does not match with the sha256 created in cmd.
Code -
val decode =
String(
Base64.decode(
responseJws!!.apkCertificateDigestSha256!![0],
Base64.DEFAULT
),
StandardCharsets.UTF_8
)
The output -
���A�`�$�;#=���/��c�n ?Ut���~
This is not matching 43:16:E2:63:DB:2A:53:7C:7D:BB:E9:80:7B:05:1C:74:7C:84:66:A2.
Update-
Found some ref but dont really know how to achieve this.
Ref1
How do I do the matching?
I have used SafetyNet API for accessing device's runtime env. I have kept signing certificate of app on server to verify its sha256 against what we get in the SafetyNet response. Below are the steps you can refer if applies to you too.
Get SHA256 fingerprint of signing X509Certificate
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] der = cert.getEncoded();
md.update(der);
byte[] sha256 = md.digest();
Encode sha256 to base64 string
String checksum = Base64.getEncoder().encodeToString(sha256)
Match checksum with apkCertificateDigestSha256 of SafetyNet response
I think this can help you
1.Find AttestationStatement file in GG example. and add this function:
public String bytesToHex(byte[] bytes) {
StringBuffer result = new StringBuffer();
for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
return result.toString();
}
2.Find getApkCertificateDigestSha256 function and edit like this:
public byte[][] getApkCertificateDigestSha256() {
byte[][] certs = new byte[apkCertificateDigestSha256.length][];
for (int i = 0; i < apkCertificateDigestSha256.length; i++) {
certs[i] = Base64.decodeBase64(apkCertificateDigestSha256[i]);
System.out.println(bytesToHex(certs[i]));
}
return certs;
}
3.Find process() function in OnlineVerrify and add like this:
if (stmt.getApkPackageName() != null && stmt.getApkDigestSha256() != null) {
System.out.println("APK package name: " + stmt.getApkPackageName());
System.out.println("APK digest SHA256: " + Arrays.toString(stmt.getApkDigestSha256()));
System.out.println("APK certificate digest SHA256: " +
Arrays.deepToString(stmt.getApkCertificateDigestSha256()));
}
Now, run and you'll see the SHA-256 and let compare.
Not: there is no ":" charactor bettwen sha-256 generated cause i'm lazy. ^^.
Check the code here as reference on how to do the validations: https://github.com/Gralls/SafetyNetSample/blob/master/Server/src/main/java/pl/patryk/springer/safetynet/Main.kt
I just found it while searching for the same thing, and all credit goes to the person that owns the repo.
public class Starter {
static String keystore_location = "C:\\Users\\<your_user>\\.android\\debug.keystore";
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(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] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
public static void main(String[] args) {
try {
File file = new File(keystore_location);
InputStream is = new FileInputStream(file);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
String password = "android"; // This is the default password
keystore.load(is, password.toCharArray());
Enumeration<String> enumeration = keystore.aliases();
while(enumeration.hasMoreElements()) {
String alias = enumeration.nextElement();
System.out.println("alias name: " + alias);
Certificate certificate = keystore.getCertificate(alias);
System.out.println(certificate.toString());
System.out.println(certificate.getEncoded());
final MessageDigest md = MessageDigest.getInstance("SHA-1");
final MessageDigest md2 = MessageDigest.getInstance("SHA-256");
final byte[] der = certificate.getEncoded();
md.update(der);
md2.update(der);
final byte[] digest = md.digest();
final byte[] digest2 = md2.digest();
System.out.println(bytesToHex(digest));
System.out.println(bytesToHex(digest2));
byte[] encoded = Base64.getEncoder().encode(digest2);
System.out.println(encoded);
String checksum = Base64.getEncoder().encodeToString(digest2);
System.out.println(checksum); // This should match apkCertificateDigestSha256
}
} catch (java.security.cert.CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
Now Google depreciated SafetyAPI and Introduced PlayIntegrity API for attestation. PlayIntegrity Service provides the response as follows.
{
"tokenPayloadExternal": {
"accountDetails": {
"appLicensingVerdict": "LICENSED"
},
"appIntegrity": {
"appRecognitionVerdict": "PLAY_RECOGNIZED",
"certificateSha256Digest": ["pnpa8e8eCArtvmaf49bJE1f5iG5-XLSU6w1U9ZvI96g"],
"packageName": "com.test.android.safetynetsample",
"versionCode": "4"
},
"deviceIntegrity": {
"deviceRecognitionVerdict": ["MEETS_DEVICE_INTEGRITY"]
},
"requestDetails": {
"nonce": "SafetyNetSample1654058651834",
"requestPackageName": "com.test.android.safetynetsample",
"timestampMillis": "1654058657132"
}
}}
Response contains only certificateSha256Digest of the app (The sha256 digest of app certificates) instead of having apkDigestSha256 and apkCertificateDigestSha256.
How do we validate the received certificateSha256Digest at server?
If the app is deployed in Google PlayStore then follow the below steps
Download the App signing key certificate from Google Play Console (If you are using managed signing key) otherwise download Upload key certificate and then find checksum of the certificate.
public static Certificate getCertificate(String certificatePath)throws Exception {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
FileInputStream in = new FileInputStream(certificatePath);
Certificate certificate = certificateFactory.generateCertificate(in);
in.close();
return certificate;
}
Generate checksum of the certificate
Certificate x509Cert = getCertificate("<Path of file>/deployment_cert.der");
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] x509Der = x509Cert.getEncoded();
md.update(x509Der);
byte[] sha256 = md.digest();
String checksum = Base64.getEncoder().encodeToString(sha256);
Then compare checksum with received certificateSha256Digest
String digest = jwsResponse.tokenPayloadExternal.appIntegrity.certificateSha256Digest;
if(checksum.contains(digest)){
//
}
I utilised TripleDES encryption in my Android app and it returns the expected string as required. However, when implementing the same feature in iOS using CommonCrypto, the returned string is different than what was expected.
public String encrypt(String message, String secretKey) throws Exception {
MessageDigest md = MessageDigest.getInstance("md5");
byte[] digestOfPassword = md.digest(secretKey.getBytes("utf-8"));
byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
for (int j = 0, k = 16; j < 8;) {
keyBytes[k++] = keyBytes[j++];
}
KeySpec keySpec = new DESedeKeySpec(keyBytes);
SecretKey key = SecretKeyFactory.getInstance("DESede").generateSecret(keySpec);
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] plainTextBytes = message.getBytes("utf-8");
byte[] buf = cipher.doFinal(plainTextBytes);
byte[] base64Bytes = Base64.encodeBase64(buf);
String base64EncryptedString = new String(base64Bytes);
return base64EncryptedString;
}
iOS Code
+ (NSData*)transformData:(NSData*)inputData operation:(CCOperation)operation withPassword:(NSString*)password
{
NSData* key = [self keyFromPassword:password];
//NSData* iv = [self ivFromPassword:password];
NSMutableData* outputData = [NSMutableData dataWithLength:(inputData.length + kCCBlockSize3DES)];
size_t outLength;
CCCryptorStatus result = CCCrypt(operation, kCCAlgorithm3DES, kCCAlgorithmDES, key.bytes, key.length, nil, inputData.bytes, inputData.length, outputData.mutableBytes, outputData.length, &outLength);
if (result != kCCSuccess)
return nil;
[outputData setLength:outLength];
return outputData;
}
+ (NSData*)keyFromPassword:(NSString*)password
{
NSString* key = [password copy];
int length = kCCKeySize3DES;
while (key.length < length)
key = [key stringByAppendingString:password];
if (key.length > length)
key = [key substringToIndex:length];
return [key dataUsingEncoding:NSUTF8StringEncoding];
}
+ (NSData*)ivFromPassword:(NSString*)password
{
NSString* key = [password copy];
int length = 8;
while (key.length < length)
key = [key stringByAppendingString:password];
if (key.length > length)
key = [key substringToIndex:length];
return [key dataUsingEncoding:NSUTF8StringEncoding];
}
It may be helpful to note that the key being used is a 20 byte key. I am limited to the 20-byte key. The android app automatically takes care of this limitation with Array.copyOf function, however, despite zero padding and repeating the first characters of the key as the last eight characters, I still cannot replicate the output in iOS.
Any help will be appreciated.
I'm trying do some encrypt something using 3des on the iOS that must match the results from java and .NET.
Java code is :
public class EncryptionHelper {
// Encrypts string and encode in Base64
public static String encryptText(String plainText,String key, String IV) throws Exception {
// ---- Use specified 3DES key and IV from other source --------------
byte[] plaintext = plainText.getBytes();//input
byte[] tdesKeyData = key.getBytes();// your encryption key
byte[] myIV = IV.getBytes();// initialization vector
Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec(tdesKeyData, "DESede");
IvParameterSpec ivspec = new IvParameterSpec(myIV);
c3des.init(Cipher.ENCRYPT_MODE, myKey, ivspec);
byte[] cipherText = c3des.doFinal(plaintext);
String encryptedString = Base64.encodeToString(cipherText,
Base64.DEFAULT);
// return Base64Coder.encodeString(new String(cipherText));
return encryptedString;
}
}
and iOS code for the same is :
-(NSString*)new3DESwithoperand:(NSString*)plaintext encryptOrDecrypt:(CCOperation)encryptorDecrypt key:(NSString*)key initVec:(NSString*)initVec
{
NSData* data = [plaintext dataUsingEncoding:NSUTF8StringEncoding];
const void *vplainText = [data bytes];;
size_t plainTextBufferSize = [data length];
NSLog(#"%#, Length: %u",[data description],[data length]);
size_t bufferPtrSize = (plainTextBufferSize + kCCBlockSize3DES) & ~(kCCBlockSize3DES - 1);
NSLog(#"%zu, sizof of uint8_t: %zu",bufferPtrSize, sizeof(uint8_t));
size_t movedBytes = 0;
uint8_t *bufferPtr = malloc( bufferPtrSize * sizeof(uint8_t));
NSLog(#"%zu",sizeof(bufferPtr));
memset((void*)bufferPtr, 0x0, bufferPtrSize);
NSLog(#"%zu",sizeof(bufferPtr));
const void * vkey = [[NSData base64DataFromString:key] bytes];
const void *vinitVec = [[NSData base64DataFromString:initVec] bytes];
NSLog(#"vinitvec: %#",[[NSData base64DataFromString:initVec] description]);
CCCryptorStatus ccStatus;
ccStatus = CCCrypt(encryptorDecrypt,
kCCAlgorithm3DES,
kCCOptionPKCS7Padding & kCCModeCBC,
vkey,
kCCKeySize3DES,
vinitVec,
vplainText,
plainTextBufferSize,
(void*)bufferPtr,
bufferPtrSize,
&movedBytes);
NSData* result = [NSData dataWithBytes:(const void*)bufferPtr length:(NSUInteger)movedBytes];
NSString* str = [NSString base64StringFromData:result length:result.length];
NSLog(#"%#",str);
return str;
}
This code successfully encrypts and decrypts a string. However, it does not match the results from .NET and java.
Thank you
Have found a solution for the above problem of encryption value generated different on iOS and .NET or Java.
Solution:
1. In Android and .NET you have to use a key of size 16 Bytes (eg: key="1234567890123456")
In iOS you need to use a key size of 24 bytes but the generation of key makes a little difference.
Use the same key as used in Android or .NET (16 bytes) and append it with the first 8 Bytes of the same key.
key16Byte = "1234567890123456" //Android and .NET key
key24Byte = key16Byte + "12345678" //ios and Java key, Replicated first 8 Bytes of 16Byte key
//new24ByteKey = "123456789012345612345678"
Remove "& kCCModeCBC" from CCypher Mode.
Some values require bytes in CCCrypt function which I have changed in the below mentioned code below. like keyData, encryptData.
Reason for different encryption generated:
Android and .NET - It takes 16Byte key and internally replicates, and generates a 24Byte key.
Java - It throws an Exception "Invalid key length", if you provide a 16Byte key value.
iOS - It generates encryption value with 16Byte and 24Byte both values without throwing any exception, so is the reason we get a different encryption generated in case of 16Byte key.
Java Code
public class EncryptionHelper {
// Encrypts string and encode in Base64
public static String encryptText(String plainText,String key, String IV) throws Exception {
// ---- Use specified 3DES key and IV from other source --------------
byte[] plaintext = plainText.getBytes();//input
byte[] tdesKeyData = key.getBytes();// your encryption key
byte[] myIV = IV.getBytes();// initialization vector
Cipher c3des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
SecretKeySpec myKey = new SecretKeySpec(tdesKeyData, "DESede");
IvParameterSpec ivspec = new IvParameterSpec(myIV);
c3des.init(Cipher.ENCRYPT_MODE, myKey, ivspec);
byte[] cipherText = c3des.doFinal(plaintext);
String encryptedString = Base64.encodeToString(cipherText,
Base64.DEFAULT);
// return Base64Coder.encodeString(new String(cipherText));
return encryptedString;
}
iOS Code:
- (NSString *)encrypt:(NSString *)encryptValue key:(NSString *)key24Byte IV:(NSString *)IV{
// first of all we need to prepare key
if([key length] != 24)
return #"Require 24 byte key, call function generate24ByteKeySameAsAndroidDotNet with a 16Byte key same as used in Android and .NET"; //temporary error message
NSData *keyData = [key24Byte dataUsingEncoding:NSUTF8StringEncoding];
// our key is ready, let's prepare other buffers and moved bytes length
NSData *encryptData = [encryptValue dataUsingEncoding:NSUTF8StringEncoding];
size_t resultBufferSize = [encryptData length] + kCCBlockSize3DES;
unsigned char resultBuffer[resultBufferSize];
size_t moved = 0;
// DES-CBC requires an explicit Initialization Vector (IV)
// IV - second half of md5 key
NSMutableData *ivData = [[IV dataUsingEncoding:NSUTF8StringEncoding]mutableCopy];
NSMutableData *iv = [NSMutableData dataWithData:ivData];
CCCryptorStatus cryptorStatus = CCCrypt(kCCEncrypt, kCCAlgorithm3DES,
kCCOptionPKCS7Padding , [keyData bytes],
kCCKeySize3DES, [iv bytes],
[encryptData bytes], [encryptData length],
resultBuffer, resultBufferSize, &moved);
if (cryptorStatus == kCCSuccess) {
return [[NSData dataWithBytes:resultBuffer length:moved] base64EncodedStringWithOptions:0];
} else {
return nil;
}
}
iOS
-(NSString *)generate24ByteKeySameAsAndroidDotNet:(NSString *)key{
NSString *new24ByteKey = key;
;
new24ByteKey = [new24ByteKey stringByAppendingString:[key substringWithRange:NSMakeRange(0, 8)]];
return new24ByteKey;
}
As #Jugal Desai mentioned, the key difference between iOS and Android/.Net is that the later ones automatically fill the key (with size 16 bytes) with another 8 bytes from the start of the key! You saved me :)
Here I provide the simple fix in Swift 3:
....
YOUR_KEY_SIZE_16 = YOUR_KEY_SIZE_16 + YOUR_KEY_SIZE_16[0...7]
....
Sample complete code (MD5 for key hash + ECB + PKCS7Padding) with base64 result:
func tripleDesEncrypt(keyString: String, pass: String) -> String{
let keyData = keyString.data(using: .utf8)!
var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH))
_ = digestData.withUnsafeMutableBytes {digestBytes in
keyData.withUnsafeBytes {messageBytes in
CC_MD5(messageBytes, CC_LONG(keyData.count), digestBytes)
}
}
digestData = digestData + digestData[0...7]
let data = pass.data(using: .utf8)!
let dataNS = data as NSData
let cryptData = NSMutableData(length: Int(dataNS.length) + kCCBlockSize3DES)!
let keyLength = size_t(kCCKeySize3DES)
let operation: CCOperation = UInt32(kCCEncrypt)
let algoritm: CCAlgorithm = UInt32(kCCAlgorithm3DES)
let options: CCOptions = UInt32(kCCOptionECBMode + kCCOptionPKCS7Padding)
var numBytesEncrypted :size_t = 0
let cryptStatus = CCCrypt(operation,
algoritm,
options,
(digestData as NSData).bytes,
keyLength,
nil,
dataNS.bytes,
dataNS.length,
cryptData.mutableBytes,
cryptData.length,
&numBytesEncrypted)
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.length = Int(numBytesEncrypted)
// Not all data is a UTF-8 string so Base64 is used
let base64cryptString = cryptData.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength76Characters)
return base64cryptString
} else {
print("Error: \(cryptStatus)")
}
return ""
}
How serious in decrypted?
- (NSString *)encrypt:(NSString *)encryptValue key:(NSString *)key24Byte IV:(NSString *)IV{
// first of all we need to prepare key
if([key length] != 24)
return #"Require 24 byte key, call function generate24ByteKeySameAsAndroidDotNet with a 16Byte key same as used in Android and .NET"; //temporary error message
NSData *keyData = [key24Byte dataUsingEncoding:NSUTF8StringEncoding];
// our key is ready, let's prepare other buffers and moved bytes length
NSData *encryptData = [encryptValue dataUsingEncoding:NSUTF8StringEncoding];
size_t resultBufferSize = [encryptData length] + kCCBlockSize3DES;
unsigned char resultBuffer[resultBufferSize];
size_t moved = 0;
// DES-CBC requires an explicit Initialization Vector (IV)
// IV - second half of md5 key
NSMutableData *ivData = [[IV dataUsingEncoding:NSUTF8StringEncoding]mutableCopy];
NSMutableData *iv = [NSMutableData dataWithData:ivData];
CCCryptorStatus cryptorStatus = CCCrypt(kCCEncrypt, kCCAlgorithm3DES,
kCCOptionPKCS7Padding , [keyData bytes],
kCCKeySize3DES, [iv bytes],
[encryptData bytes], [encryptData length],
resultBuffer, resultBufferSize, &moved);
if (cryptorStatus == kCCSuccess) {
return [[NSData dataWithBytes:resultBuffer length:moved] base64EncodedStringWithOptions:0];
} else {
return nil;
}
}
How do I check if the signature of my app matches the signature of the certificate that I used to sign it?
This is how I should be able to get the certificates fingerprint:
public String getCertificateFingerprint() throws NameNotFoundException, CertificateException, NoSuchAlgorithmException {
PackageManager pm = context.getPackageManager();
String packageName =context.getPackageName();
int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;
packageInfo = pm.getPackageInfo(packageName, flags);
Signature[] signatures = packageInfo.signatures;
byte[] cert = signatures[0].toByteArray();
InputStream input = new ByteArrayInputStream(cert);
CertificateFactory cf = null;
cf = CertificateFactory.getInstance("X509");
X509Certificate c = null;
c = (X509Certificate) cf.generateCertificate(input);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] publicKey = md.digest(c.getPublicKey().getEncoded());
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < publicKey.length; i++) {
String appendString = Integer.toHexString(0xFF & publicKey[i]);
if (appendString.length() == 1)
hexString.append("0");
hexString.append(appendString);
}
return hexString.toString();
}
This is how I should be able to get the fingerprint of my certificate:
keytool -v -list -keystore filenameandpath
My problem is, that these two give back different results.
Could someone point out what I'm screwing up?
You are computing the MD5 hash of the wrong data. The fingerprint of a certificate is a hash (MD5, SHA1, SHA256, etc.) of the raw certificate. I.e., you should be computing the hash of these bytes:
byte[] cert = signatures[0].toByteArray();
E.g., the following computes a SHA1 fingerprint, just change SHA1 to MD5 if you prefer.
public String computeFingerPrint(final byte[] certRaw) {
String strResult = "";
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA1");
md.update(certRaw);
for (byte b : md.digest()) {
strAppend = Integer.toString(b & 0xff, 16);
if (strAppend.length() == 1)
strResult += "0";
strResult += strAppend;
}
strResult = strResult.toUpperCase(DATA_LOCALE);
}
catch (NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
return strResult;
}
You can open the apk as a zip file and filter the ascii text from the binary content of META-INF/CERT.RSA and check there is it you who singed it.
try:
final void initVerify(Certificate certificate)
from: http://developer.android.com/reference/java/security/Signature.html
Use your code for collecting the fingerprint on the device in "test" mode -- meaning you have temporary code to emit that fingerprint to the log (or elsewhere). Be sure to test this using your production signing key, not the debug key!
Once you know from the device's perspective, you can remove the temporary code and elsewhere you can compare to what you've previously determined to be the key.
Be aware though that you're probably doing this to prevent someone from modifying your app and re-signing it with another key, but someone with the ability to do that also has the ability to modify your key checking. This is a problem that can be addressed with additional obfuscation but you'll need to come up with your own solution to minimize the chance of an attacker knowing what to look for.
the code below:
c.getPublicKey().getEncoded()
it should be like this
c.getEncoded()
i think md5 check by keytool is check the certfile,not the publickey
I'm trying to implement a system where A generates an RSA key pair and sends the public key to B. B then generates an AES key and encrypts it using the public key, sending the result back to A. A then decrypts the AES key using its RSA private key, encrypts the data using the AES key and send it to B, who can then decrypt it using the AES key.
I've got this all working on the Android side, but I can't get the iPhone side to play ball (I'm new to Objective C so that's probably why!)
Initially, I was getting an error 9809 when decrypting the AES key using the RSA private key, which unhelpfully translates to a general error. Researching the error points to the padding (I'm using PKCS1 Padding) being the problem, switching to No Padding allowed the iPhone client to decrypt successfully, but the decrypted AES key is different from the one generated on the Android client.
Objective C is very new to me and I'm sure I'm just making a schoolboy error, can anyone point me in the right direction please?
iPhone RSA key pair generation
static const unsigned char _encodedRSAEncryptionOID[15] = {
/* Sequence of length 0xd made up of OID followed by NULL */
0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00
};
NSData * publicTag = [publicKeyIdentifier dataUsingEncoding:NSUTF8StringEncoding];
// Now lets extract the public key - build query to get bits
NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];
[queryPublicKey setObject:(__bridge id) kSecClassKey
forKey:(__bridge id) kSecClass];
[queryPublicKey setObject:publicTag
forKey:(__bridge id) kSecAttrApplicationTag];
[queryPublicKey setObject:(__bridge id) kSecAttrKeyTypeRSA
forKey:(__bridge id) kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES]
forKey:(__bridge id) kSecReturnData];
CFTypeRef pk;
OSStatus err = SecItemCopyMatching((__bridge CFDictionaryRef)queryPublicKey, &pk);
NSData* publicKeyBits = (__bridge_transfer NSData*)pk;
if (err != noErr) {
return nil;
}
// OK - that gives us the "BITSTRING component of a full DER
// encoded RSA public key - we now need to build the rest
unsigned char builder[15];
NSMutableData * encKey = [[NSMutableData alloc] init];
int bitstringEncLength;
// When we get to the bitstring - how will we encode it?
if ([publicKeyBits length ] + 1 < 128 )
bitstringEncLength = 1 ;
else
bitstringEncLength = (([publicKeyBits length ] +1 ) / 256 ) + 2 ;
// Overall we have a sequence of a certain length
builder[0] = 0x30; // ASN.1 encoding representing a SEQUENCE
// Build up overall size made up of -
// size of OID + size of bitstring encoding + size of actual key
size_t i = sizeof(_encodedRSAEncryptionOID) + 2 + bitstringEncLength +
[publicKeyBits length];
size_t j = encodeLength(&builder[1], i);
[encKey appendBytes:builder length:j +1];
// First part of the sequence is the OID
[encKey appendBytes:_encodedRSAEncryptionOID
length:sizeof(_encodedRSAEncryptionOID)];
// Now add the bitstring
builder[0] = 0x03;
j = encodeLength(&builder[1], [publicKeyBits length] + 1);
builder[j+1] = 0x00;
[encKey appendBytes:builder length:j + 2];
// Now the actual key
[encKey appendData:publicKeyBits];
// Now translate the result to a Base64 string
Base64* base64 = [[Base64 alloc] init];
NSString* ret = [base64 encode:encKey];
return ret;
Re-creating the public key, generating the AES key and encrypting it on Android
(note the getBytes(...) and getString(...) just do some base64 encoding.decoding)
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256, new SecureRandom());
SecretKey secretKey = keyGen.generateKey();
byte[] publicKeyBytes = getBytes(publicKey.getKey());
PublicKey rsaKey = KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, rsaKey);
String keyEncoded = getString(key);
return getString(encryptedKeyBytes));
Decrypting the AES key on iPhone
Base64* base64 = [[Base64 alloc] init];
NSData* cipherText = [base64 decode:textBase64];
const uint8_t *cipherBuffer = (const uint8_t*)[cipherText bytes];
size_t cipherBufferSize = strlen((char *) cipherBuffer);
uint8_t *plainBuffer = (uint8_t *)calloc(SecKeyGetBlockSize(publicKey), sizeof(uint8_t));
size_t plainBufferSize = SecKeyGetBlockSize(publicKey);
OSStatus status = SecKeyDecrypt(privateKey,
kSecPaddingPKCS1,
&cipherBuffer[0],
cipherBufferSize,
&plainBuffer[0],
&plainBufferSize
);
NSData* finalData = [[NSData alloc] initWithBytes:plainBuffer length:plainBufferSize];
NSString *result = [base64 encode:finalData];
return result;
EDIT: I think I've narrowed this down a bit, the following code from the Decrypting the AES key part of my code:
NSData* cipherText = [base64 decode:text];
NSLog(#"cipherText %#", cipherText);
const uint8_t *cipherBuffer = (const uint8_t*)[cipherText bytes];
NSLog(#"cipherBuffer %s", cipherBuffer);
size_t cipherBufferSize = strlen((char *) cipherBuffer);
NSLog(#"cipherBufferSize %zd", cipherBufferSize);
Produces the following output in the console:
cipherText <31226275 cc56069a e96b7f6f 0fbee853 32d07de6 436755c9 e27b88a6 04176947 d57f7108 de68e5b8 49595e9f 09bceb30 1d615927 c205f205 eb644fa7 bff6c02b 885605de eb5bd4ee 473bb4d3 df768017 24552706 ea67f347 2952614e ad63f3c6 eb0022d3 a0513afa 0e59ba63 cb5c9787 a40ecad4 a866fdc7 26b60cc2 088a3499 a84c0595 fb1c2be8 5c85b88d 7856b4bd 655f6fec 905ca221 d6bb03c0 7329410b b235ef8f 1ef97a64 7fabb280 90118ff7 4b1e91f6 162134fc 5cbf962e 813e39e7 993b0fb9 e3c4b30c ef6a7b90 9d64c41a 1211ab34 c2c52235 d2ec3b65 d1314cee 70eafe65 f4a6c5e4 660cf889 4540a784 d14cc5a8 49a12c43 c76f7f03 5fbcd44f>
cipherBuffer 1"buÃVöÈkoæËS2–}ÊCgU…‚{à¶iG’qfihÂ∏IY^ü ºÎ0aY'¬ÚÎdOßøˆ¿+àVfiÎ[‘ÓG;¥”flvÄ$U'ÍgÛG)RaN≠cÛ∆Î
cipherBufferSize 97
Occasionally however, it comes out with a cipher buffer size of 256 as expected and the decryption works perfectly! I know I must be missing something obvious?
Your issue is with the strlen function, which does not work on binary data in general, it only works on binary data that represents text and it concluded with a zero valued byte (\0). Instead you should use the actual size of the ciphertext.
So currently your code block will fail if the ciphertext contains a zero valued byte, or if it is not directly followed by a zero valued byte.