I am creating a SHA384 hash. I want to decode that hash. Is there any possible way to do this? Please help
Following is the code to get hash
public String getHash(String message) {
String algorithm = "SHA384";
String hex = "";
try {
byte[] buffer = message.getBytes();
MessageDigest md = MessageDigest.getInstance(algorithm);
md.update(buffer);
byte[] digest = md.digest();
for(int i = 0 ; i < digest.length ; i++) {
int b = digest[i] & 0xff;
if (Integer.toHexString(b).length() == 1) hex = hex + "0";
hex = hex + Integer.toHexString(b);
}
return hex;
} catch(NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
A cryptographically secure hashing function is a function such that a given arbitrary length input is processed into a fixed length output in such a way that is not reversible (computationally infeasible). Such functions include MD5 and the SHA (Secure Hash Algorithm) family (1, 224, 256, 384, 512, etc).
Once you take the hash of the input there is no going back to the original input. This property can be used for verification of message integrity as hashing the identical message produces a identical hash.
The website you visited simply stores hashes and their inputs side by side and does a database lookup for your hash to attempt to find a possible input (if it was previously added to the database).
Related
I am seeing a small percentage of production users randomly report this exception related to encrypting/decrypting strings with Xamarin.Android but unfortunately I cannot reproduce it.
What could cause this and/or how could I reproduce the exception so that I can figure out a fix/workaround?
[CryptographicException: Bad PKCS7 padding. Invalid length 147.]
Mono.Security.Cryptography.SymmetricTransform.ThrowBadPaddingException(PaddingMode padding, Int32 length, Int32 position):0
Mono.Security.Cryptography.SymmetricTransform.FinalDecrypt(System.Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount):0
Mono.Security.Cryptography.SymmetricTransform.TransformFinalBlock(System.Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount):0
System.Security.Cryptography.CryptoStream.FlushFinalBlock():0
com.abc.mobile.shared.Security+PasswordEncoder.DecryptWithByteArray(System.String strText, System.String strEncrypt):0
EDIT: Here's the code I am using to encrypt/decrypt
private string EncryptWithByteArray(string inPassword, string inByteArray)
{
byte[] tmpKey = new byte[20];
tmpKey = System.Text.Encoding.UTF8.GetBytes(inByteArray.Substring(0, 8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] inputArray = System.Text.Encoding.UTF8.GetBytes(inPassword);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(tmpKey, mInitializationVector), CryptoStreamMode.Write);
cs.Write(inputArray, 0, inputArray.Length);
cs.FlushFinalBlock();
return Convert.ToBase64String(ms.ToArray());
}
private string DecryptWithByteArray (string strText, string strEncrypt)
{
try
{
byte[] tmpKey = new byte[20];
tmpKey = System.Text.Encoding.UTF8.GetBytes (strEncrypt.Substring (0, 8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider ();
Byte[] inputByteArray = Convert.FromBase64String (strText);
MemoryStream ms = new MemoryStream ();
CryptoStream cs = new CryptoStream (ms, des.CreateDecryptor (tmpKey, mInitializationVector), CryptoStreamMode.Write);
cs.Write (inputByteArray, 0, inputByteArray.Length);
try {
cs.FlushFinalBlock();
} catch (Exception ex) {
throw(ex);
}
System.Text.Encoding encoding = System.Text.Encoding.UTF8;
return encoding.GetString(ms.ToArray());
}
catch (Exception ex)
{
throw ex;
}
}
EDIT 2:
The encryption key is always the local Device ID. Here's how I am getting this:
TelephonyManager telephonyMgr = Application.Context.GetSystemService(Context.TelephonyService) as TelephonyManager;
string deviceId = telephonyMgr.DeviceId == null ? "UNAVAILABLE" : telephonyMgr.DeviceId;
Here's an example of how it's called:
string mByteArray = GetDeviceId();
string mEncryptedString = EncryptWithByteArray(stringToEncrypt, mByteArray);
string mDecryptedString = DecryptWithByteArray(mEncryptedString, mByteArray);
You have not provided much details about your use case but I would say this is happening because you are not using the same cipher settings during the encryption and decryption operations. Symmetric ciphers require you to use exactly the same settings/parameters during the data encryption and also decryption. For example for AES CBC you would need to use exactly the same key, IV, cipher mode and padding on both devices. It is best to set these setting explicitly in the code:
System.Security.Cryptography.RijndaelManaged aes = new System.Security.Cryptography.RijndaelManaged();
aes.Key = new byte[] { ... };
aes.IV = new byte[] { ... };
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
If you are sure you are using the same settings then you should also consider scenario that some data get corrupted or altered during the network transfer.
Edit after some code fragments have been provided:
Decryption method you have provided does not work for me at all so I have put together all your samples and turned them into the code which does the same thing as yours but uses IMO a slightly cleaner approach. For example this code uses more robust "key derivation" (please forgive me cryptoguys) and it has also passed basic code analysis.
You should be able to easily use public methods to do what you need:
string plainData = "This information should be encrypted";
string encryptedData = EncryptStringified(plainData);
string decryptedData = DecryptStringified(encryptedData);
if (plainData != decryptedData)
throw new Exception("Decryption failed");
Implementation and private methods follows:
/// <summary>
/// Encrypts string with the key derived from device ID
/// </summary>
/// <returns>Base64 encoded encrypted data</returns>
/// <param name="stringToEncrypt">String to encrypt</param>
public string EncryptStringified(string stringToEncrypt)
{
if (stringToEncrypt == null)
throw new ArgumentNullException("stringToEncrypt");
byte[] key = DeviceIdToDesKey();
byte[] plainData = Encoding.UTF8.GetBytes(stringToEncrypt);
byte[] encryptedData = Encrypt(key, plainData);
return Convert.ToBase64String(encryptedData);
}
/// <summary>
/// Decrypts Base64 encoded data with the key derived from device ID
/// </summary>
/// <returns>Decrypted string</returns>
/// <param name="b64DataToDecrypt">Base64 encoded data to decrypt</param>
public string DecryptStringified(string b64DataToDecrypt)
{
if (b64DataToDecrypt == null)
throw new ArgumentNullException("b64DataToDecrypt");
byte[] key = DeviceIdToDesKey();
byte[] encryptedData = Convert.FromBase64String(b64DataToDecrypt);
byte[] decryptedData = Decrypt(key, encryptedData);
return Encoding.UTF8.GetString(decryptedData);
}
private byte[] DeviceIdToDesKey()
{
TelephonyManager telephonyMgr = Application.Context.GetSystemService(Context.TelephonyService) as TelephonyManager;
string deviceId = telephonyMgr.DeviceId ?? "UNAVAILABLE";
// Compute hash of device ID so we are sure enough bytes have been gathered for the key
byte[] bytes = null;
using (SHA1 sha1 = SHA1.Create())
bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(deviceId));
// Get last 8 bytes from device ID hash as a key
byte[] desKey = new byte[8];
Array.Copy(bytes, bytes.Length - desKey.Length, desKey, 0, desKey.Length);
return desKey;
}
private byte[] Encrypt(byte[] key, byte[] plainData)
{
if (key == null)
throw new ArgumentNullException("key");
if (plainData == null)
throw new ArgumentNullException("plainData");
using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
{
if (!desProvider.ValidKeySize(key.Length * 8))
throw new CryptographicException("Key with invalid size has been specified");
desProvider.Key = key;
// desProvider.IV should be automatically filled with random bytes when DESCryptoServiceProvider instance is created
desProvider.Mode = CipherMode.CBC;
desProvider.Padding = PaddingMode.PKCS7;
using (MemoryStream encryptedStream = new MemoryStream())
{
// Write IV at the beginning of memory stream
encryptedStream.Write(desProvider.IV, 0, desProvider.IV.Length);
// Perform encryption and append encrypted data to the memory stream
using (ICryptoTransform encryptor = desProvider.CreateEncryptor())
{
byte[] encryptedData = encryptor.TransformFinalBlock(plainData, 0, plainData.Length);
encryptedStream.Write(encryptedData, 0, encryptedData.Length);
}
return encryptedStream.ToArray();
}
}
}
private byte[] Decrypt(byte[] key, byte[] encryptedData)
{
if (key == null)
throw new ArgumentNullException("key");
if (encryptedData == null)
throw new ArgumentNullException("encryptedData");
using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
{
if (!desProvider.ValidKeySize(key.Length * 8))
throw new CryptographicException("Key with invalid size has been specified");
desProvider.Key = key;
if (encryptedData.Length <= desProvider.IV.Length)
throw new CryptographicException("Too short encrypted data has been specified");
// Read IV from the beginning of encrypted data
// Note: New byte array needs to be created because data written to desprovider.IV are ignored
byte[] iv = new byte[desProvider.IV.Length];
Array.Copy(encryptedData, 0, iv, 0, iv.Length);
desProvider.IV = iv;
desProvider.Mode = CipherMode.CBC;
desProvider.Padding = PaddingMode.PKCS7;
// Remove IV from the beginning of encrypted data and perform decryption
using (ICryptoTransform decryptor = desProvider.CreateDecryptor())
return decryptor.TransformFinalBlock(encryptedData, desProvider.IV.Length, encryptedData.Length - desProvider.IV.Length);
}
}
It is really hard to tell what exactly was problem with your code because your decryption method did not work for me at all - most likely because it is using CryptoStream in write mode for decryption which seems a little odd to me.
So much for the code. Now let's get to encryption which is really really weak. It is more just an obfuscation that should protect the data from being accidentally displayed in plain text form (some people use BASE64 encoding for the same thing). The main cause of this is relatively old encryption algorithm and easily predictable encryption key. AFAIK every application running on the same device can read device ID without any privileges. That means any application can decrypt your data. Of course your SQLite database is probably accessible only to your application but that can no longer be true if you remove the SD card or root your phone. To make this a little better you could for example ask user to provide a password and then use it to derive unique encryption key but that is completely different problem. Anyway I am not really sure what you are trying to achieve with this encryption - it may be fully sufficient for your needs even if it can be considered to be weak.
Hope this helps.
The scenario is the next:
I want to upload image to the server. But before uploading the file I have to send the SHA1 checksum of that file so the server could check if the file is already uploaded so I don't upload it again.
The problem is that for the same file I don't get the same SHA1 checksum in my app and on the server side.
Here is the code in my Android app:
public static String getSHA1FromFileContent(String filename)
throws NoSuchAlgorithmException, IOException {
final MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
InputStream is = new BufferedInputStream(new FileInputStream(filename));
final byte[] buffer = new byte[1024];
for (int read = 0; (read = is.read(buffer)) != -1;) {
messageDigest.update(buffer, 0, read);
}
is.close();
// Convert the byte to hex format
Formatter formatter = new Formatter();
for (final byte b : messageDigest.digest()) {
formatter.format("%02x", b);
}
String res = formatter.toString();
formatter.close();
return res;
}
And here is the code on the server side:
def hashFile(f):
sha1 = hashlib.sha1()
if hasattr(f, 'multiple_chunks') and f.multiple_chunks():
for c in f.chunks():
sha1.update(c)
else:
try:
sha1.update(f.read())
finally:
f.close()
return sha1.hexdigest()
What is the problem and why do I get different SHA1 checksums?
Turned out there was some server side image editing before generating the sha1 sum that wasn't meant to be done in this scenario. They made changes on the server side and now this is working perfectly.
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 struggeling with code from this page: http://www.androidsnippets.com/encrypt-decrypt-between-android-and-php
I want to send data from a server to an Android application and vice versa, but it should be sent as an encrypted string. However, I manage to encrypt and decrypt the string in PHP. But on Android the application crashes with the following error message when decrypting:
java.lang.Exception: [decrypt] unable to parse ' as integer.
This error occours here in the for-loop:
public static byte[] hexToBytes(String str) {
if (str==null) {
return null;
} else if (str.length() < 2) {
return null;
} else {
int len = str.length() / 2;
byte[] buffer = new byte[len];
for (int i=0; i<len; i++) {
buffer[i] = (byte) Integer.parseInt(str.substring(i*2,i*2+2),16);
}
System.out.println("Buffer: " + buffer);
return buffer;
}
}
This is by the way the string that should be decrypted: f46d86e65fe31ed46920b20255dd8ea6
You're talking about encrypting and decrypting, but you're showing code which simply turns numeric bytes (such as 0x4F) into strings (such as "4F") -- which may be relevant to your transfer of data (if you cannot transfer binary format), but completely unrelated to encryption/decryption.
Since the Android code you have contains only a single Integer parse, have you examined the input you're giving it? str.substring(i*2,i*2+2) apparently contains data other than [0-9A-F] when the exception occurs. You should start by examining the string you've received and comparing it to what you sent, to make sure they agree and they only contain hexadecimal characters.
Edit -- passing the string "f46d86e65fe31ed46920b20255dd8ea6" through your hexToBytes() function works flawlessly. Your input is probably not what you think it is.
I'm reading a lot since some weeks to implement an encrypt/decrypt algoritm for my Android application. I'm implementing a license key that is downloaded from my website and stored in the external storage of my Android device. the application read the content of the file and decrypt it using the server public key (yes i know that i should with client private key but it's ok for my purpose). The problem is that the final string has a lot of black square with question mark inside. i've read a lot of other posts here on stackoverflow, but i think that the "only" problem is that, even if there should be 10 chars in the string, the string is long 255 bytes (with 2048 bit RSA key) and the remaining chars are filled with black "". Why the newPlainText var is not long as "Hello World!" ? Here below my code... Many thanks in advance!
public boolean licenseValid() throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{
java.io.File file = new java.io.File(Environment.getExternalStorageDirectory().toString() ,
"/folder/file.lic");
byte[] fileBArray = new byte[(int)file.length()];
FileInputStream fis = new FileInputStream(file);
// Read in the bytes
int offset = 0;
int numRead = 0;
while (offset < fileBArray.length
&& (numRead=fis.read(fileBArray, offset, fileBArray.length-offset)) >= 0) {
offset += numRead;
}
// Ensure all the bytes have been read in
if (offset < fileBArray.length) {
throw new IOException("Could not completely read file "+file.getName());
}
fis.close();
// Decrypt the ciphertext using the public key
PublicKey pubKey = readKeyFromFile();
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, pubKey);
byte[] newPlainText = cipher.doFinal(fileBArray);
// THE FOLLOWING TOAST PRINTS MANY <?> AND THAN THE DECRYPTED MESSAGE. THE TOTAL NUMBER OF CHARACTERS IS 255, EVEN IF I CHANGE ENCRYPTED TEXT!
toast(String.valueOf(cipher.doFinal(fileBArray).length));
if (new String(newPlainText, "utf-8").compareTo("Hello World!") == 0)
return true;
else
return false;
}
PublicKey readKeyFromFile() throws IOException {
Resources myResources = getResources();
//public key filename "pub.lic"
InputStream is = myResources.openRawResource(R.raw.pub);
ObjectInputStream oin =
new ObjectInputStream(new BufferedInputStream(is));
try {
BigInteger m = (BigInteger) oin.readObject();
BigInteger e = (BigInteger) oin.readObject();
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(m, e);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey pubKey = fact.generatePublic(keySpec);
return pubKey;
} catch (Exception e) {
throw new RuntimeException("Spurious serialisation error", e);
} finally {
oin.close();
}
}
If you encrypt with RSA the input and output are always the same length as the key. In your case, that should be 256 bytes (=2048 bits), so first check your code, you are missing a byte.
When the input is shorter, you need to apply a padding, and it looks like your server and client are using a different one. Cipher.getInstance("RSA") will use the platform default, which is probably different for Android and Java SE. You need to specify the padding explicitly in both programs for this to work. Something like this:
Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
BTW, you really don't want to distribute the private key with your app, so using the public key is the right thing to do. (Whether your whole encryption scheme is secure is another matter though).