Related
I'm trying to port the following Java code to C#, but so far it still says that the signature is invalid.
private static String generateSignStr(Map<String, String> params, String key) {
StringBuilder sb = new StringBuilder();
params.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> {
if (sb.length() > 0) {
sb.append('&');
}
sb.append(entry.getKey()).append('=');
sb.append(entry.getValue());
});
sb.append('&').append("api_secret")
.append('=').append(key);
return sb.toString();
}
private static String sign(String target) {
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
log.error("Fail to get MD5 instance");
return null;
}
md.update(target.getBytes());
byte[] dg = md.digest();
StringBuilder output = new StringBuilder(dg.length * 2);
for (byte dgByte : dg) {
int current = dgByte & 0xff;
if (current < 16) {
output.append("0");
}
output.append(Integer.toString(current, 16));
}
return output.toString();
}
private static string GenerateSign(Dictionary<string, object> query, string apiSecret)
{
var sb = new StringBuilder();
var queryParameterString = string.Join("&",
query.Where(kvp => !string.IsNullOrWhiteSpace(kvp.Value.ToString()))
.Select(kvp => $"{kvp.Key}={HttpUtility.UrlEncode(kvp.Value.ToString())}"));
sb.Append(queryParameterString);
if (sb.Length > 0)
{
sb.Append('&');
}
sb.Append("api_secret=").Append(apiSecret);
return sb.ToString();
}
private static string Sign(string source)
{
using var md5 = MD5.Create();
var sourceBytes = Encoding.UTF8.GetBytes(source);
var hash = md5.ComputeHash(sourceBytes);
return BitConverter.ToString(hash).Replace("-", string.Empty).ToLowerInvariant();
}
Edit:
This fixed it. However, it would be nice if someone knows a way to lexicographically sort the dictionary inside that method just like the Java code.
var #params = new Dictionary<string, object>
{
{ "api_key", _apiKey },
{ "req_time", now },
{ "op", "sub.personal" }
};
var javaSorted = #params.OrderBy(item => item.Key, StringComparer.Ordinal)
.ToDictionary(i => i.Key, i => i.Value);
var signature = Sign(GenerateSign(javaSorted, _apiSecret));
In GenerateSign method you can just create instance of SortedDictionary based on dictionary passed as parameter:
private static string GenerateSign(Dictionary<string, object> query, string apiSecret)
{
var sortedDict = new SortedDictionary<string, object>(query, StringComparer.Ordinal);
// rest of the method
}
Or you can do even better (note the important change from Dictionary to IDictionary):
private static string GenerateSign(IDictionary<string, object> query, string apiSecret)
{
query = new SortedDictionary<string, object>(query, StringComparer.Ordinal);
// rest of the method
}
I'm working on an Android application that needs to crypt (and then to decrypt) file on the file system. I wrote an android test to test the code that I found on the web and I adapted for my needed. I try with to crypt a simple text and then try to decrypt it. The problem is when I try to decrypt it, some strange character appears at the beginning of the content that I want to crypt/decrypt. For example, I try to crypt/decrypt a string like this:
Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)
And I received
X��YK�P���$BProgramming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)Concentration - Programming Music 0100 (Part 4)
The test code is
#Test
public void test() throws IOException, GeneralSecurityException {
String input = "Concentration - Programming Music 0100 (Part 4)";
for (int i=0;i<10;i++) {
input+=input;
}
String password = EncryptSystem.encrypt(new ByteArrayInputStream(input.getBytes(Charset.forName("UTF-8"))), new File(this.context.getFilesDir(), "test.txt"));
InputStream inputStream = EncryptSystem.decrypt(password, new File(this.context.getFilesDir(), "test.txt"));
//creating an InputStreamReader object
InputStreamReader isReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
//Creating a BufferedReader object
BufferedReader reader = new BufferedReader(isReader);
StringBuilder sb = new StringBuilder();
String str;
while ((str = reader.readLine()) != null) {
sb.append(str);
}
System.out.println(sb.toString());
Assert.assertEquals(input, sb.toString());
}
The class code is:
import android.os.Build;
import android.os.Process;
import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.crypto.*;
public class EncryptSystem {
public static class SecretKeys {
private SecretKey confidentialityKey;
private byte[] iv;
/**
* An aes key derived from a base64 encoded key. This does not generate the
* key. It's not random or a PBE key.
*
* #param keysStr a base64 encoded AES key / hmac key as base64(aesKey) : base64(hmacKey).
* #return an AES and HMAC key set suitable for other functions.
*/
public static SecretKeys of(String keysStr) throws InvalidKeyException {
String[] keysArr = keysStr.split(":");
if (keysArr.length != 2) {
throw new IllegalArgumentException("Cannot parse aesKey:iv");
} else {
byte[] confidentialityKey = Base64.decode(keysArr[0], BASE64_FLAGS);
if (confidentialityKey.length != AES_KEY_LENGTH_BITS / 8) {
throw new InvalidKeyException("Base64 decoded key is not " + AES_KEY_LENGTH_BITS + " bytes");
}
byte[] iv = Base64.decode(keysArr[1], BASE64_FLAGS);
/* if (iv.length != HMAC_KEY_LENGTH_BITS / 8) {
throw new InvalidKeyException("Base64 decoded key is not " + HMAC_KEY_LENGTH_BITS + " bytes");
}*/
return new SecretKeys(
new SecretKeySpec(confidentialityKey, 0, confidentialityKey.length, CIPHER),
iv);
}
}
public SecretKeys(SecretKey confidentialityKeyIn, byte[] i) {
setConfidentialityKey(confidentialityKeyIn);
iv = new byte[i.length];
System.arraycopy(i, 0, iv, 0, i.length);
}
public SecretKey getConfidentialityKey() {
return confidentialityKey;
}
public void setConfidentialityKey(SecretKey confidentialityKey) {
this.confidentialityKey = confidentialityKey;
}
#Override
public String toString() {
return Base64.encodeToString(getConfidentialityKey().getEncoded(), BASE64_FLAGS)
+ ":" + Base64.encodeToString(this.iv, BASE64_FLAGS);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SecretKeys that = (SecretKeys) o;
return confidentialityKey.equals(that.confidentialityKey) &&
Arrays.equals(iv, that.iv);
}
#Override
public int hashCode() {
int result = Objects.hash(confidentialityKey);
result = 31 * result + Arrays.hashCode(iv);
return result;
}
public byte[] getIv() {
return this.iv;
}
}
// If the PRNG fix would not succeed for some reason, we normally will throw an exception.
// If ALLOW_BROKEN_PRNG is true, however, we will simply log instead.
private static final boolean ALLOW_BROKEN_PRNG = false;
private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String CIPHER = "AES";
private static final int AES_KEY_LENGTH_BITS = 128;
private static final int IV_LENGTH_BYTES = 16;
private static final int PBE_ITERATION_COUNT = 10000;
private static final int PBE_SALT_LENGTH_BITS = AES_KEY_LENGTH_BITS; // same size as key output
private static final String PBE_ALGORITHM = "PBKDF2WithHmacSHA1";
//Made BASE_64_FLAGS public as it's useful to know for compatibility.
public static final int BASE64_FLAGS = Base64.NO_WRAP;
//default for testing
static final AtomicBoolean prngFixed = new AtomicBoolean(false);
private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final int HMAC_KEY_LENGTH_BITS = 256;
public static SecretKeys generateKey() throws GeneralSecurityException {
fixPrng();
KeyGenerator keyGen = KeyGenerator.getInstance(CIPHER);
// No need to provide a SecureRandom or set a seed since that will
// happen automatically.
keyGen.init(AES_KEY_LENGTH_BITS);
SecretKey confidentialityKey = keyGen.generateKey();
return new SecretKeys(confidentialityKey, generateIv());
}
private static void fixPrng() {
if (!prngFixed.get()) {
synchronized (PrngFixes.class) {
if (!prngFixed.get()) {
PrngFixes.apply();
prngFixed.set(true);
}
}
}
}
private static byte[] randomBytes(int length) throws GeneralSecurityException {
fixPrng();
SecureRandom random = new SecureRandom();
byte[] b = new byte[length];
random.nextBytes(b);
return b;
}
private static byte[] generateIv() throws GeneralSecurityException {
return randomBytes(IV_LENGTH_BYTES);
}
private static String keyString(SecretKeys keys) {
return keys.toString();
}
public static SecretKeys generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
fixPrng();
//Get enough random bytes for both the AES key and the HMAC key:
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
PBE_ITERATION_COUNT, AES_KEY_LENGTH_BITS + HMAC_KEY_LENGTH_BITS);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance(PBE_ALGORITHM);
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
// Split the random bytes into two parts:
byte[] confidentialityKeyBytes = copyOfRange(keyBytes, 0, AES_KEY_LENGTH_BITS / 8);
byte[] integrityKeyBytes = copyOfRange(keyBytes, AES_KEY_LENGTH_BITS / 8, AES_KEY_LENGTH_BITS / 8 + HMAC_KEY_LENGTH_BITS / 8);
//Generate the AES key
SecretKey confidentialityKey = new SecretKeySpec(confidentialityKeyBytes, CIPHER);
return new SecretKeys(confidentialityKey, generateIv());
}
private static byte[] copyOfRange(byte[] from, int start, int end) {
int length = end - start;
byte[] result = new byte[length];
System.arraycopy(from, start, result, 0, length);
return result;
}
public static SecretKeys generateKeyFromPassword(String password, String salt) throws GeneralSecurityException {
return generateKeyFromPassword(password, Base64.decode(salt, BASE64_FLAGS));
}
public static String encrypt(InputStream inputStream, File fileToWrite)
throws GeneralSecurityException {
SecretKeys secretKeys = generateKey();
return encrypt(inputStream, secretKeys, fileToWrite);
}
public static InputStream decrypt(String secretKey, File fileToRead) throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, FileNotFoundException {
SecretKeys secretKeys = SecretKeys.of(secretKey);
Cipher aesCipherForDecryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secretKeys.getConfidentialityKey(),
new IvParameterSpec(secretKeys.getIv()));
return new CipherInputStream(new FileInputStream(fileToRead), aesCipherForDecryption);
}
private static String encrypt(InputStream inputStream, SecretKeys secretKeys, File fileToWrite)
throws GeneralSecurityException {
byte[] iv = generateIv();
Cipher aesCipherForEncryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(iv));
saveFile(inputStream, aesCipherForEncryption, fileToWrite);
/*
* Now we get back the IV that will actually be used. Some Android
* versions do funny stuff w/ the IV, so this is to work around bugs:
*/
/*iv = aesCipherForEncryption.getIV();
//byte[] byteCipherText = aesCipherForEncryption.doFinal(plaintext);
byte[] ivCipherConcat = CipherTextIvMac.ivCipherConcat(iv, byteCipherText);
byte[] integrityMac = generateMac(ivCipherConcat, secretKeys.getIntegrityKey());
return new CipherTextIvMac(byteCipherText, iv, integrityMac);*/
return secretKeys.toString();
}
private static boolean saveFile(InputStream inputStream, Cipher aesCipherForEncryption, File fileToWrite) {
try {
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
/*long fileSize = body.contentLength();*/
long fileSizeDownloaded = 0;
outputStream = new CipherOutputStream(new FileOutputStream(fileToWrite), aesCipherForEncryption);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
}
outputStream.flush();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
public static final class PrngFixes {
private static final int VERSION_CODE_JELLY_BEAN = 16;
private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial();
/**
* Hidden constructor to prevent instantiation.
*/
private PrngFixes() {
}
/**
* Applies all fixes.
*
* #throws SecurityException if a fix is needed but could not be
* applied.
*/
public static void apply() {
applyOpenSSLFix();
installLinuxPRNGSecureRandom();
}
/**
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if
* the fix is not needed.
*
* #throws SecurityException if the fix is needed but could not be
* applied.
*/
private static void applyOpenSSLFix() throws SecurityException {
if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
|| (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
// No need to apply the fix
return;
}
try {
// Mix in the device- and invocation-specific seed.
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class).invoke(null, generateSeed());
// Mix output of Linux PRNG into OpenSSL's PRNG
int bytesRead = (Integer) Class
.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_load_file", String.class, long.class)
.invoke(null, "/dev/urandom", 1024);
if (bytesRead != 1024) {
throw new IOException("Unexpected number of bytes read from Linux PRNG: "
+ bytesRead);
}
} catch (Exception e) {
if (ALLOW_BROKEN_PRNG) {
Log.w(PrngFixes.class.getSimpleName(), "Failed to seed OpenSSL PRNG", e);
} else {
throw new SecurityException("Failed to seed OpenSSL PRNG", e);
}
}
}
/**
* Installs a Linux PRNG-backed {#code SecureRandom} implementation as
* the default. Does nothing if the implementation is already the
* default or if there is not need to install the implementation.
*
* #throws SecurityException if the fix is needed but could not be
* applied.
*/
private static void installLinuxPRNGSecureRandom() throws SecurityException {
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
// No need to apply the fix
return;
}
// Install a Linux PRNG-based SecureRandom implementation as the
// default, if not yet installed.
Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
// Insert and check the provider atomically.
// The official Android Java libraries use synchronized methods for
// insertProviderAt, etc., so synchronizing on the class should
// make things more stable, and prevent race conditions with other
// versions of this code.
synchronized (java.security.Security.class) {
if ((secureRandomProviders == null)
|| (secureRandomProviders.length < 1)
|| (!secureRandomProviders[0].getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider"))) {
Security.insertProviderAt(new PrngFixes.LinuxPRNGSecureRandomProvider(), 1);
}
// Assert that new SecureRandom() and
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
// by the Linux PRNG-based SecureRandom implementation.
SecureRandom rng1 = new SecureRandom();
if (!rng1.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) {
if (ALLOW_BROKEN_PRNG) {
Log.w(PrngFixes.class.getSimpleName(),
"new SecureRandom() backed by wrong Provider: " + rng1.getProvider().getClass());
return;
} else {
throw new SecurityException("new SecureRandom() backed by wrong Provider: "
+ rng1.getProvider().getClass());
}
}
SecureRandom rng2 = null;
try {
rng2 = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
if (ALLOW_BROKEN_PRNG) {
Log.w(PrngFixes.class.getSimpleName(), "SHA1PRNG not available", e);
return;
} else {
new SecurityException("SHA1PRNG not available", e);
}
}
if (!rng2.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) {
if (ALLOW_BROKEN_PRNG) {
Log.w(PrngFixes.class.getSimpleName(),
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: "
+ rng2.getProvider().getClass());
return;
} else {
throw new SecurityException(
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: "
+ rng2.getProvider().getClass());
}
}
}
}
/**
* {#code Provider} of {#code SecureRandom} engines which pass through
* all requests to the Linux PRNG.
*/
private static class LinuxPRNGSecureRandomProvider extends Provider {
public LinuxPRNGSecureRandomProvider() {
super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses"
+ " /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some apps
// explicitly request a SHA1PRNG SecureRandom and we thus need
// to prevent them from getting the default implementation whose
// output may have low entropy.
put("SecureRandom.SHA1PRNG", PrngFixes.LinuxPRNGSecureRandom.class.getName());
put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
}
}
/**
* {#link SecureRandomSpi} which passes all requests to the Linux PRNG (
* {#code /dev/urandom}).
*/
public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
/*
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a
* seed are passed through to the Linux PRNG (/dev/urandom).
* Instances of this class seed themselves by mixing in the current
* time, PID, UID, build fingerprint, and hardware serial number
* (where available) into Linux PRNG.
*
* Concurrency: Read requests to the underlying Linux PRNG are
* serialized (on sLock) to ensure that multiple threads do not get
* duplicated PRNG output.
*/
private static final File URANDOM_FILE = new File("/dev/urandom");
private static final Object sLock = new Object();
/**
* Input stream for reading from Linux PRNG or {#code null} if not
* yet opened.
*
* #GuardedBy("sLock")
*/
private static DataInputStream sUrandomIn;
/**
* Output stream for writing to Linux PRNG or {#code null} if not
* yet opened.
*
* #GuardedBy("sLock")
*/
private static OutputStream sUrandomOut;
/**
* Whether this engine instance has been seeded. This is needed
* because each instance needs to seed itself if the client does not
* explicitly seed it.
*/
private boolean mSeeded;
#Override
protected void engineSetSeed(byte[] bytes) {
try {
OutputStream out;
synchronized (sLock) {
out = getUrandomOutputStream();
}
out.write(bytes);
out.flush();
} catch (IOException e) {
// On a small fraction of devices /dev/urandom is not
// writable Log and ignore.
Log.w(PrngFixes.class.getSimpleName(), "Failed to mix seed into "
+ URANDOM_FILE);
} finally {
mSeeded = true;
}
}
#Override
protected void engineNextBytes(byte[] bytes) {
if (!mSeeded) {
// Mix in the device- and invocation-specific seed.
engineSetSeed(generateSeed());
}
try {
DataInputStream in;
synchronized (sLock) {
in = getUrandomInputStream();
}
synchronized (in) {
in.readFully(bytes);
}
} catch (IOException e) {
throw new SecurityException("Failed to read from " + URANDOM_FILE, e);
}
}
#Override
protected byte[] engineGenerateSeed(int size) {
byte[] seed = new byte[size];
engineNextBytes(seed);
return seed;
}
private DataInputStream getUrandomInputStream() {
synchronized (sLock) {
if (sUrandomIn == null) {
// NOTE: Consider inserting a BufferedInputStream
// between DataInputStream and FileInputStream if you need
// higher PRNG output performance and can live with future PRNG
// output being pulled into this process prematurely.
try {
sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE));
} catch (IOException e) {
throw new SecurityException("Failed to open " + URANDOM_FILE
+ " for reading", e);
}
}
return sUrandomIn;
}
}
private OutputStream getUrandomOutputStream() throws IOException {
synchronized (sLock) {
if (sUrandomOut == null) {
sUrandomOut = new FileOutputStream(URANDOM_FILE);
}
return sUrandomOut;
}
}
}
/**
* Generates a device- and invocation-specific seed to be mixed into the
* Linux PRNG.
*/
private static byte[] generateSeed() {
try {
ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer);
seedBufferOut.writeLong(System.currentTimeMillis());
seedBufferOut.writeLong(System.nanoTime());
seedBufferOut.writeInt(Process.myPid());
seedBufferOut.writeInt(Process.myUid());
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
seedBufferOut.close();
return seedBuffer.toByteArray();
} catch (IOException e) {
throw new SecurityException("Failed to generate seed", e);
}
}
/**
* Gets the hardware serial number of this device.
*
* #return serial number or {#code null} if not available.
*/
private static String getDeviceSerialNumber() {
// We're using the Reflection API because of Build.SERIAL is only
// available since API Level 9 (Gingerbread, Android 2.3).
try {
return (String) Build.class.getField("SERIAL").get(null);
} catch (Exception ignored) {
return null;
}
}
private static byte[] getBuildFingerprintAndDeviceSerial() {
StringBuilder result = new StringBuilder();
String fingerprint = Build.FINGERPRINT;
if (fingerprint != null) {
result.append(fingerprint);
}
String serial = getDeviceSerialNumber();
if (serial != null) {
result.append(serial);
}
try {
return result.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported");
}
}
}
}
Any idea about what I'm in wrong? Thank you in advance
Finally, I solve by myself. I post the solution just to help anybody that in the future will look for a similar situation. I mistake to retrieve the iv array in the encrypt method, I was generating another iv vector instead of using the one contained in secretKeys.
private static String encrypt(InputStream inputStream, SecretKeys secretKeys, File fileToWrite)
throws GeneralSecurityException {
Cipher aesCipherForEncryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(secretKeys.getIv()));
saveFile(inputStream, aesCipherForEncryption, fileToWrite);
return secretKeys.toString();
}
This is actually exactly what I am looking for but in Java:
Encrypted cookies in Chrome
The mentioned "Windows Data Protection API (DPAPI)" I have found here for Java:
http://jdpapi.sourceforge.net/
but its "outdated" or only for 32bit platform.
SQLite connection and result is of course working that's what I got atm:
public void getDecryptedValue() {
try {
Statement stmt = connection.createStatement();
String sql = "SELECT * FROM cookies";
ResultSet rs = stmt.executeQuery(sql);
int cookieCount = 1;
while (rs.next()) {
log.debug("####### Cookie " + cookieCount + " ############");
String host_key = rs.getString("host_key");
log.debug(host_key);
byte[] encrypted_value = rs.getBytes("encrypted_value");
//this ofc outputs "nonsense". here the decryption is needed.
log.debug(encrypted_value.toString());
cookieCount++;
}
rs.close();
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
I searched now for 2 days found nothing helpful my next approach would be to
decrypt/encrypt triple DES encryption with (Windows username? and password? what is used as seed if you have no password?) but I really don't know how I should do that with the BLOB I get from the SQLite DB or even start.
Edit: I am digging deeper with that atm : http://n8henrie.com/2014/05/decrypt-chrome-cookies-with-python/
I spent a long time on this myself. Here are some classes you can use to decrypt cookies for Chrome on Mac, Windows, and Linux. I didn't implement inserting cookies, but I imagine you have most of everything you need with this code to add that functionality.
It requires some third party libraries (JNI Windows encryption wrappers and SQLite) so I am linking to the Maven project I am using the code in if you need to see the code in context.
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import com.sun.jna.platform.win32.Crypt32Util;
public class Test {
public static void main(String[] args) throws IOException {
ChromeBrowser chrome = new ChromeBrowser();
Set<Cookie> cookies = chrome.getCookiesForDomain("stackoverflow.com");
for(Cookie cookie : cookies){
System.out.println(cookie.toString());
}
}
public static abstract class Cookie {
protected String name;
protected byte[] encryptedValue;
protected Date expires;
protected String path;
protected String domain;
protected boolean secure;
protected boolean httpOnly;
protected File cookieStore;
public Cookie(String name, byte[] encryptedValue, Date expires, String path, String domain, boolean secure, boolean httpOnly, File cookieStore) {
this.name = name;
this.encryptedValue = encryptedValue;
this.expires = expires;
this.path = path;
this.domain = domain;
this.secure = secure;
this.httpOnly = httpOnly;
this.cookieStore = cookieStore;
}
public String getName() {
return name;
}
public byte[] getEncryptedValue() {
return encryptedValue;
}
public Date getExpires() {
return expires;
}
public String getPath() {
return path;
}
public String getDomain() {
return domain;
}
public boolean isSecure() {
return secure;
}
public boolean isHttpOnly() {
return httpOnly;
}
public File getCookieStore(){
return cookieStore;
}
public abstract boolean isDecrypted();
}
public static class DecryptedCookie extends Cookie {
private String decryptedValue;
public DecryptedCookie(String name, byte[] encryptedValue, String decryptedValue, Date expires, String path, String domain, boolean secure, boolean httpOnly, File cookieStore) {
super(name, encryptedValue, expires, path, domain, secure, httpOnly, cookieStore);
this.decryptedValue = decryptedValue;
}
public String getDecryptedValue(){
return decryptedValue;
}
#Override
public boolean isDecrypted() {
return true;
}
#Override
public String toString() {
return "Cookie [name=" + name + ", value=" + decryptedValue + "]";
}
}
public static class EncryptedCookie extends Cookie {
public EncryptedCookie(String name, byte[] encryptedValue, Date expires, String path, String domain, boolean secure, boolean httpOnly, File cookieStore) {
super(name, encryptedValue, expires, path, domain, secure, httpOnly, cookieStore);
}
#Override
public boolean isDecrypted() {
return false;
}
#Override
public String toString() {
return "Cookie [name=" + name + " (encrypted)]";
}
}
public static class OS {
public static String getOsArchitecture() {
return System.getProperty("os.arch");
}
public static String getOperatingSystem() {
return System.getProperty("os.name");
}
public static String getOperatingSystemVersion() {
return System.getProperty("os.version");
}
public static String getIP() throws UnknownHostException {
InetAddress ip = InetAddress.getLocalHost();
return ip.getHostAddress();
}
public static String getHostname() throws UnknownHostException {
return InetAddress.getLocalHost().getHostName();
}
public static boolean isWindows() {
return (getOperatingSystem().toLowerCase().indexOf("win") >= 0);
}
public static boolean isMac() {
return (getOperatingSystem().toLowerCase().indexOf("mac") >= 0);
}
public static boolean isLinux() {
return (getOperatingSystem().toLowerCase().indexOf("nix") >= 0 || getOperatingSystem().toLowerCase().indexOf("nux") >= 0 || getOperatingSystem().toLowerCase().indexOf("aix") > 0 );
}
public static boolean isSolaris() {
return (getOperatingSystem().toLowerCase().indexOf("sunos") >= 0);
}
}
public static abstract class Browser {
/**
* A file that should be used to make a temporary copy of the browser's cookie store
*/
protected File cookieStoreCopy = new File(".cookies.db");
/**
* Returns all cookies
*/
public Set<Cookie> getCookies() {
HashSet<Cookie> cookies = new HashSet<Cookie>();
for(File cookieStore : getCookieStores()){
cookies.addAll(processCookies(cookieStore, null));
}
return cookies;
}
/**
* Returns cookies for a given domain
*/
public Set<Cookie> getCookiesForDomain(String domain) {
HashSet<Cookie> cookies = new HashSet<Cookie>();
for(File cookieStore : getCookieStores()){
cookies.addAll(processCookies(cookieStore, domain));
}
return cookies;
}
/**
* Returns a set of cookie store locations
* #return
*/
protected abstract Set<File> getCookieStores();
/**
* Processes all cookies in the cookie store for a given domain or all
* domains if domainFilter is null
*
* #param cookieStore
* #param domainFilter
* #return
*/
protected abstract Set<Cookie> processCookies(File cookieStore, String domainFilter);
/**
* Decrypts an encrypted cookie
* #param encryptedCookie
* #return
*/
protected abstract DecryptedCookie decrypt(EncryptedCookie encryptedCookie);
}
/**
* An implementation of Chrome cookie decryption logic for Mac, Windows, and Linux installs
*
* References:
* 1) http://n8henrie.com/2014/05/decrypt-chrome-cookies-with-python/
* 2) https://github.com/markushuber/ssnoob
*
* #author Ben Holland
*/
public static class ChromeBrowser extends Browser {
private String chromeKeyringPassword = null;
/**
* Returns a set of cookie store locations
* #return
*/
#Override
protected Set<File> getCookieStores() {
HashSet<File> cookieStores = new HashSet<File>();
// pre Win7
cookieStores.add(new File(System.getProperty("user.home") + "\\Application Data\\Google\\Chrome\\User Data\\Default\\Cookies"));
// Win 7+
cookieStores.add(new File(System.getProperty("user.home") + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies"));
// Mac
cookieStores.add(new File(System.getProperty("user.home") + "/Library/Application Support/Google/Chrome/Default/Cookies"));
// Linux
cookieStores.add(new File(System.getProperty("user.home") + "/.config/chromium/Default/Cookies"));
return cookieStores;
}
/**
* Processes all cookies in the cookie store for a given domain or all
* domains if domainFilter is null
*
* #param cookieStore
* #param domainFilter
* #return
*/
#Override
protected Set<Cookie> processCookies(File cookieStore, String domainFilter) {
HashSet<Cookie> cookies = new HashSet<Cookie>();
if(cookieStore.exists()){
Connection connection = null;
try {
cookieStoreCopy.delete();
Files.copy(cookieStore.toPath(), cookieStoreCopy.toPath());
// load the sqlite-JDBC driver using the current class loader
Class.forName("org.sqlite.JDBC");
// create a database connection
connection = DriverManager.getConnection("jdbc:sqlite:" + cookieStoreCopy.getAbsolutePath());
Statement statement = connection.createStatement();
statement.setQueryTimeout(30); // set timeout to 30 seconds
ResultSet result = null;
if(domainFilter == null || domainFilter.isEmpty()){
result = statement.executeQuery("select * from cookies");
} else {
result = statement.executeQuery("select * from cookies where host_key like \"%" + domainFilter + "%\"");
}
while (result.next()) {
String name = result.getString("name");
byte[] encryptedBytes = result.getBytes("encrypted_value");
String path = result.getString("path");
String domain = result.getString("host_key");
boolean secure = result.getBoolean("secure");
boolean httpOnly = result.getBoolean("httponly");
Date expires = result.getDate("expires_utc");
EncryptedCookie encryptedCookie = new EncryptedCookie(name,
encryptedBytes,
expires,
path,
domain,
secure,
httpOnly,
cookieStore);
DecryptedCookie decryptedCookie = decrypt(encryptedCookie);
if(decryptedCookie != null){
cookies.add(decryptedCookie);
} else {
cookies.add(encryptedCookie);
}
cookieStoreCopy.delete();
}
} catch (Exception e) {
e.printStackTrace();
// if the error message is "out of memory",
// it probably means no database file is found
} finally {
try {
if (connection != null){
connection.close();
}
} catch (SQLException e) {
// connection close failed
}
}
}
return cookies;
}
/**
* Decrypts an encrypted cookie
* #param encryptedCookie
* #return
*/
#Override
protected DecryptedCookie decrypt(EncryptedCookie encryptedCookie) {
byte[] decryptedBytes = null;
if(OS.isWindows()){
try {
decryptedBytes = Crypt32Util.cryptUnprotectData(encryptedCookie.getEncryptedValue());
} catch (Exception e){
decryptedBytes = null;
}
} else if(OS.isLinux()){
try {
byte[] salt = "saltysalt".getBytes();
char[] password = "peanuts".toCharArray();
char[] iv = new char[16];
Arrays.fill(iv, ' ');
int keyLength = 16;
int iterations = 1;
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength * 8);
SecretKeyFactory pbkdf2 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] aesKey = pbkdf2.generateSecret(spec).getEncoded();
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(new String(iv).getBytes()));
// if cookies are encrypted "v10" is a the prefix (has to be removed before decryption)
byte[] encryptedBytes = encryptedCookie.getEncryptedValue();
if (new String(encryptedCookie.getEncryptedValue()).startsWith("v10")) {
encryptedBytes = Arrays.copyOfRange(encryptedBytes, 3, encryptedBytes.length);
}
decryptedBytes = cipher.doFinal(encryptedBytes);
} catch (Exception e) {
decryptedBytes = null;
}
} else if(OS.isMac()){
// access the decryption password from the keyring manager
if(chromeKeyringPassword == null){
try {
chromeKeyringPassword = getMacKeyringPassword("Chrome Safe Storage");
} catch (IOException e) {
decryptedBytes = null;
}
}
try {
byte[] salt = "saltysalt".getBytes();
char[] password = chromeKeyringPassword.toCharArray();
char[] iv = new char[16];
Arrays.fill(iv, ' ');
int keyLength = 16;
int iterations = 1003;
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength * 8);
SecretKeyFactory pbkdf2 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] aesKey = pbkdf2.generateSecret(spec).getEncoded();
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(new String(iv).getBytes()));
// if cookies are encrypted "v10" is a the prefix (has to be removed before decryption)
byte[] encryptedBytes = encryptedCookie.getEncryptedValue();
if (new String(encryptedCookie.getEncryptedValue()).startsWith("v10")) {
encryptedBytes = Arrays.copyOfRange(encryptedBytes, 3, encryptedBytes.length);
}
decryptedBytes = cipher.doFinal(encryptedBytes);
} catch (Exception e) {
decryptedBytes = null;
}
}
if(decryptedBytes == null){
return null;
} else {
return new DecryptedCookie(encryptedCookie.getName(),
encryptedCookie.getEncryptedValue(),
new String(decryptedBytes),
encryptedCookie.getExpires(),
encryptedCookie.getPath(),
encryptedCookie.getDomain(),
encryptedCookie.isSecure(),
encryptedCookie.isHttpOnly(),
encryptedCookie.getCookieStore());
}
}
/**
* Accesses the apple keyring to retrieve the Chrome decryption password
* #param application
* #return
* #throws IOException
*/
private static String getMacKeyringPassword(String application) throws IOException {
Runtime rt = Runtime.getRuntime();
String[] commands = {"security", "find-generic-password","-w", "-s", application};
Process proc = rt.exec(commands);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String result = "";
String s = null;
while ((s = stdInput.readLine()) != null) {
result += s;
}
return result;
}
}
}
I have two applications that interact using Thrift. They share the same secret key and I need to encrypt their messages. It makes sense to use symmetric algorithm (AES, for example), but I haven't found any library to do this. So I made a research and see following options:
Use built-in SSL support
I can use built-in SSL support, establish secure connection and use my secret key just as authentication token. It requires to install certificates in addition to the secret key they already have, but I don't need to implement anything except checking that secret key received from client is the same as secret key stored locally.
Implement symmetric encryption
So far, there are following options:
Extend TSocket and override write() and read() methods and en- / decrypt data in them. Will have increasing of traffic on small writes. For example, if TBinaryProtocol writes 4-bytes integer, it will take one block (16 bytes) in encrypted state.
Extend TSocket and wrap InputStream and OutputStream with CipherInputStream and CipherOutputStream. CipherOutputStream will not encrypt small byte arrays immediately, updating Cipher with them. After we have enough data, they will be encrypted and written to the underlying OutputStream. So it will wait until you add 4 4-byte ints and encrypt them then. It allows us not wasting traffic, but is also a cause of problem - if last value will not fill the block, it will be never encrypted and written to the underlying stream. It expects me to write number of bytes divisible by its block size (16 byte), but I can't do this using TBinaryProtocol.
Re-implement TBinaryProtocol, caching all writes instead of writing them to stream and encrypting in writeMessageEnd() method. Implement decryption in readMessageBegin(). I think encryption should be performed on the transport layer, not protocol one.
Please share your thoughts with me.
UPDATE
Java Implementation on Top of TFramedTransport
TEncryptedFramedTransport.java
package tutorial;
import org.apache.thrift.TByteArrayOutputStream;
import org.apache.thrift.transport.TMemoryInputTransport;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.TTransportFactory;
import javax.crypto.Cipher;
import java.security.Key;
/**
* TEncryptedFramedTransport is a buffered TTransport. It encrypts fully read message
* with the "AES/ECB/PKCS5Padding" symmetric algorithm and send it, preceeding with a 4-byte frame size.
*/
public class TEncryptedFramedTransport extends TTransport {
public static final String ALGORITHM = "AES/ECB/PKCS5Padding";
private Cipher encryptingCipher;
private Cipher decryptingCipher;
protected static final int DEFAULT_MAX_LENGTH = 0x7FFFFFFF;
private int maxLength_;
private TTransport transport_ = null;
private final TByteArrayOutputStream writeBuffer_ = new TByteArrayOutputStream(1024);
private TMemoryInputTransport readBuffer_ = new TMemoryInputTransport(new byte[0]);
public static class Factory extends TTransportFactory {
private int maxLength_;
private Key secretKey_;
public Factory(Key secretKey) {
this(secretKey, DEFAULT_MAX_LENGTH);
}
public Factory(Key secretKey, int maxLength) {
maxLength_ = maxLength;
secretKey_ = secretKey;
}
#Override
public TTransport getTransport(TTransport base) {
return new TEncryptedFramedTransport(base, secretKey_, maxLength_);
}
}
/**
* Constructor wraps around another tranpsort
*/
public TEncryptedFramedTransport(TTransport transport, Key secretKey, int maxLength) {
transport_ = transport;
maxLength_ = maxLength;
try {
encryptingCipher = Cipher.getInstance(ALGORITHM);
encryptingCipher.init(Cipher.ENCRYPT_MODE, secretKey);
decryptingCipher = Cipher.getInstance(ALGORITHM);
decryptingCipher.init(Cipher.DECRYPT_MODE, secretKey);
} catch (Exception e) {
throw new RuntimeException("Unable to initialize ciphers.");
}
}
public TEncryptedFramedTransport(TTransport transport, Key secretKey) {
this(transport, secretKey, DEFAULT_MAX_LENGTH);
}
public void open() throws TTransportException {
transport_.open();
}
public boolean isOpen() {
return transport_.isOpen();
}
public void close() {
transport_.close();
}
public int read(byte[] buf, int off, int len) throws TTransportException {
if (readBuffer_ != null) {
int got = readBuffer_.read(buf, off, len);
if (got > 0) {
return got;
}
}
// Read another frame of data
readFrame();
return readBuffer_.read(buf, off, len);
}
#Override
public byte[] getBuffer() {
return readBuffer_.getBuffer();
}
#Override
public int getBufferPosition() {
return readBuffer_.getBufferPosition();
}
#Override
public int getBytesRemainingInBuffer() {
return readBuffer_.getBytesRemainingInBuffer();
}
#Override
public void consumeBuffer(int len) {
readBuffer_.consumeBuffer(len);
}
private final byte[] i32buf = new byte[4];
private void readFrame() throws TTransportException {
transport_.readAll(i32buf, 0, 4);
int size = decodeFrameSize(i32buf);
if (size < 0) {
throw new TTransportException("Read a negative frame size (" + size + ")!");
}
if (size > maxLength_) {
throw new TTransportException("Frame size (" + size + ") larger than max length (" + maxLength_ + ")!");
}
byte[] buff = new byte[size];
transport_.readAll(buff, 0, size);
try {
buff = decryptingCipher.doFinal(buff);
} catch (Exception e) {
throw new TTransportException(0, e);
}
readBuffer_.reset(buff);
}
public void write(byte[] buf, int off, int len) throws TTransportException {
writeBuffer_.write(buf, off, len);
}
#Override
public void flush() throws TTransportException {
byte[] buf = writeBuffer_.get();
int len = writeBuffer_.len();
writeBuffer_.reset();
try {
buf = encryptingCipher.doFinal(buf, 0, len);
} catch (Exception e) {
throw new TTransportException(0, e);
}
encodeFrameSize(buf.length, i32buf);
transport_.write(i32buf, 0, 4);
transport_.write(buf);
transport_.flush();
}
public static void encodeFrameSize(final int frameSize, final byte[] buf) {
buf[0] = (byte) (0xff & (frameSize >> 24));
buf[1] = (byte) (0xff & (frameSize >> 16));
buf[2] = (byte) (0xff & (frameSize >> 8));
buf[3] = (byte) (0xff & (frameSize));
}
public static int decodeFrameSize(final byte[] buf) {
return
((buf[0] & 0xff) << 24) |
((buf[1] & 0xff) << 16) |
((buf[2] & 0xff) << 8) |
((buf[3] & 0xff));
}
}
MultiplicationServer.java
package tutorial;
import co.runit.prototype.CryptoTool;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TNonblockingServerTransport;
import java.security.Key;
public class MultiplicationServer {
public static MultiplicationHandler handler;
public static MultiplicationService.Processor processor;
public static void main(String[] args) {
try {
handler = new MultiplicationHandler();
processor = new MultiplicationService.Processor(handler);
Runnable simple = () -> startServer(processor);
new Thread(simple).start();
} catch (Exception x) {
x.printStackTrace();
}
}
public static void startServer(MultiplicationService.Processor processor) {
try {
Key key = CryptoTool.decodeKeyBase64("1OUXS3MczVFp3SdfX41U0A==");
TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(9090);
TServer server = new TNonblockingServer(new TNonblockingServer.Args(serverTransport)
.transportFactory(new TEncryptedFramedTransport.Factory(key))
.processor(processor));
System.out.println("Starting the simple server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
}
MultiplicationClient.java
package tutorial;
import co.runit.prototype.CryptoTool;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import java.security.Key;
public class MultiplicationClient {
public static void main(String[] args) {
Key key = CryptoTool.decodeKeyBase64("1OUXS3MczVFp3SdfX41U0A==");
try {
TSocket baseTransport = new TSocket("localhost", 9090);
TTransport transport = new TEncryptedFramedTransport(baseTransport, key);
transport.open();
TProtocol protocol = new TBinaryProtocol(transport);
MultiplicationService.Client client = new MultiplicationService.Client(protocol);
perform(client);
transport.close();
} catch (TException x) {
x.printStackTrace();
}
}
private static void perform(MultiplicationService.Client client) throws TException {
int product = client.multiply(3, 5);
System.out.println("3*5=" + product);
}
}
Of course, keys must be the same on the client and server. To generate and store it in Base64:
public static String generateKey() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128);
Key key = generator.generateKey();
return encodeKeyBase64(key);
}
public static String encodeKeyBase64(Key key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}
public static Key decodeKeyBase64(String encodedKey) {
byte[] keyBytes = Base64.getDecoder().decode(encodedKey);
return new SecretKeySpec(keyBytes, ALGORITHM);
}
UPDATE 2
Python Implementation on Top of TFramedTransport
TEncryptedTransport.py
from cStringIO import StringIO
from struct import pack, unpack
from Crypto.Cipher import AES
from thrift.transport.TTransport import TTransportBase, CReadableTransport
__author__ = 'Marboni'
BLOCK_SIZE = 16
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
unpad = lambda s: '' if not s else s[0:-ord(s[-1])]
class TEncryptedFramedTransportFactory:
def __init__(self, key):
self.__key = key
def getTransport(self, trans):
return TEncryptedFramedTransport(trans, self.__key)
class TEncryptedFramedTransport(TTransportBase, CReadableTransport):
def __init__(self, trans, key):
self.__trans = trans
self.__rbuf = StringIO()
self.__wbuf = StringIO()
self.__cipher = AES.new(key)
def isOpen(self):
return self.__trans.isOpen()
def open(self):
return self.__trans.open()
def close(self):
return self.__trans.close()
def read(self, sz):
ret = self.__rbuf.read(sz)
if len(ret) != 0:
return ret
self.readFrame()
return self.__rbuf.read(sz)
def readFrame(self):
buff = self.__trans.readAll(4)
sz, = unpack('!i', buff)
encrypted = StringIO(self.__trans.readAll(sz)).getvalue()
decrypted = unpad(self.__cipher.decrypt(encrypted))
self.__rbuf = StringIO(decrypted)
def write(self, buf):
self.__wbuf.write(buf)
def flush(self):
wout = self.__wbuf.getvalue()
self.__wbuf = StringIO()
encrypted = self.__cipher.encrypt(pad(wout))
encrypted_len = len(encrypted)
buf = pack("!i", encrypted_len) + encrypted
self.__trans.write(buf)
self.__trans.flush()
# Implement the CReadableTransport interface.
#property
def cstringio_buf(self):
return self.__rbuf
def cstringio_refill(self, prefix, reqlen):
while len(prefix) < reqlen:
self.readFrame()
prefix += self.__rbuf.getvalue()
self.__rbuf = StringIO(prefix)
return self.__rbuf
MultiplicationClient.py
import base64
from thrift import Thrift
from thrift.transport import TSocket
from thrift.protocol import TBinaryProtocol
from tutorial import MultiplicationService, TEncryptedTransport
key = base64.b64decode("1OUXS3MczVFp3SdfX41U0A==")
try:
transport = TSocket.TSocket('localhost', 9090)
transport = TEncryptedTransport.TEncryptedFramedTransport(transport, key)
protocol = TBinaryProtocol.TBinaryProtocol(transport)
client = MultiplicationService.Client(protocol)
transport.open()
product = client.multiply(4, 5, 'Echo!')
print '4*5=%d' % product
transport.close()
except Thrift.TException, tx:
print tx.message
As stated by JensG, sending an externally encrypted binary or supplying a layered cipher transport are the two best options. It you need a template, take a look at the TFramedTransport. It is a simple layered transport and could easily be used as a starting block for creating a TCipherTransport.
I need help to convert this code to Java for password comparison and it must run on Android.
I am specially confused in how to add the salt given in this C# Code here:
Code C#
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace CMS.Core.Utility
{
public sealed class CMSHashManager
{
private static readonly string _salt = "3D5900AE-111A-45BE-96B3-D9E4606CA793";
private static readonly int _hashIterationsMax = 10;
private CMSHashManager()
{
}
#region Public Methods
//Gets the salted hash value with predetermined iterations.
public static string GetPasswordHash(string plaintextPassword)
{
string hashData = plaintextPassword;
for (int hashLimit = 0; hashLimit < _hashIterationsMax; hashLimit++)
hashData = GetHash(_salt + hashData);
return hashData;
}
//Verifies the hash
public static bool VerifyHashedPassword(string plaintextPassword, string encryptedPassword)
{
string hashData = GetPasswordHash(plaintextPassword);
return encryptedPassword.Equals(hashData);
}
#endregion Public Methods
#region Private Methods
//Gets the hash value of the data using SHA512Managed
private static string GetHash(string unhashedData)
{
byte[] hashData = Encoding.UTF8.GetBytes(unhashedData);
// on server 2003 or higher, can use SHA512CryptoServiceProvider
//SHA512CryptoServiceProvider sha512CryptoServiceProvider = new SHA512CryptoServiceProvider();
SHA512Managed sha512CryptoServiceProvider = new SHA512Managed();
hashData = sha512CryptoServiceProvider.ComputeHash(hashData);
sha512CryptoServiceProvider.Clear();
return Convert.ToBase64String(hashData);
}
#endregion Private Methods
}
}
I have already written this java method which creates a MD5 hash:
Code Java
public String getMD5Password(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException{
MessageDigest digest = java.security.MessageDigest.getInstance("SHA-512");
digest.update(password.getBytes("UTF-16LE"));
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < messageDigest.length; i++) {
String h = Integer.toHexString(0xFF & messageDigest[i]);
while (h.length() < 2)
h = "0" + h;
hexString.append(h);
}
return hexString.toString();
}
Test
For testing purposes you can use the following case:
plaintext:12345
Encrypted:NgkuakH7UsCQwGHMQOhVXI3nW6M+1AtREY4Qx35osQo87p/whZIzy8cZU7+R7XnmyzgMzLWSvX+rTiWzfGTPsA==
I tried to reproduce your code.
For the password test it produces the following BASE64 output
Q0Y2QkI0MTBFRUJFOTAyNkU1OUZGMUNGMzU0NkYzMkI3NDZFMzE5RjQzNTc0MDM5QjU2MUI2NEQxOTQzNzRGMDRENDM0QzMyQjg3MjMwQkM1N0I0ODFDRDlEODlBNjMxQjMyNjRGQjNBQjAwMEYwNjk5Rjc0NUNEQjgzMzY1RkM=
I used the following code:
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
//import javax.xml.bind.DatatypeConverter;
import android.util.Base64;
public class Support {
private static final String SALT = "3D5900AE-111A-45BE-96B3-D9E4606CA793";
private static final int MAX_HASH_ITERATIONS = 10;
public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException {
String result = Support.GetPasswordHash("test");
System.out.println(result);
}
public static String GetPasswordHash(String plaintextPassword) throws NoSuchAlgorithmException, UnsupportedEncodingException {
String hashData = plaintextPassword;
for (int hashLimit = 0; hashLimit < MAX_HASH_ITERATIONS; hashLimit++) {
hashData = GetHash(SALT + hashData);
}
return hashData;
}
//Gets the hash value of the data using SHA512Managed
private static String GetHash(String unhashedData) throws NoSuchAlgorithmException, UnsupportedEncodingException {
return getMD5Password(unhashedData);
}
//Verifies the hash
public static boolean VerifyHashedPassword(String plaintextPassword, String encryptedPassword) throws NoSuchAlgorithmException, UnsupportedEncodingException {
String hashData = GetPasswordHash(plaintextPassword);
return encryptedPassword.equals(hashData);
}
public static String getMD5Password(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException{
MessageDigest digest = java.security.MessageDigest.getInstance("SHA-512");
digest.update(password.getBytes("UTF-16LE"));
byte messageDigest[] = digest.digest();
StringBuilder sb = new StringBuilder();
for(int iPos = 0; iPos < messageDigest.length; iPos++) {
String h = Integer.toHexString(0xFF & messageDigest[iPos]);
while (h.length() < 2) {
h = "0" + h;
}
sb.append(h);
}
String md5String = sb.toString().toUpperCase();
String res = Base64.encodeToString(md5String.getBytes(), Base64.DEFAULT);
return res;
}
}