I'm tasked with building a consumer of an API that requires an encrypted token with a seed value that is the UNIX time. The example I was shown was implemented using Java which I'm unfamiliar with, and after reading through documentation and other stack articles have been unable to find a solution.
Using the javax.crypto.SecretKey, javax.crypto.SecretKeyFactory, javax.crypto.spec.PBEKeySpec, and javax.crypto.spec.SecretKeySpec protocols, I need to generate a token similar to the below:
public class EncryptionTokenDemo {
public static void main(String args[]) {
long millis = System.currentTimeMillis();
String time = String.valueOf(millis);
String secretKey = "somekeyvalue";
int iterations = 12345;
String iters = String.valueOf(iterations);
String strToEncrypt_acctnum = "somevalue|" + time + "|" + iterations;
try {
byte[] input = strToEncrypt_acctnum.toString().getBytes("utf-8");
byte[] salt = secretKey.getBytes("utf-8");
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey tmp = factory.generateSecret(new PBEKeySpec(secretKey.toCharArray(), salt, iterations, 256));
SecretKeySpec skc = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skc);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);
String query = Base64.encodeBase64URLSafeString(cipherText);
// String query = cipherText.toString();
System.out.println("The unix time in ms is :: " + time);
System.out.println("Encrypted Token is :: " + query);
} catch (Exception e) {
System.out.println("Error while encrypting :" + e);
}
}
}
Should I be using the built-in library hashlib to implement something like this? I can't really find documentation for implementing a PBKDF2 encryption with iterations/salt as inputs. Should I be using pbkdf2? Sorry for the vague questions, I'm unfamiliar with the encryption process and feel like even just knowing what the correct constructor would be is a step in the right direction.
Yes, the Python equivalent is hashlib.pbkdf2_hmac. For example this code:
from hashlib import pbkdf2_hmac
key = pbkdf2_hmac(
hash_name = 'sha1',
password = b"somekeyvalue",
salt = b"somekeyvalue",
iterations = 12345,
dklen = 32
)
print(key)
produces the same key as your Java code.
However, the problem with this code (as mentioned in memo's comment) is the use of salt. The salt should be random and unique for each password. You can create secure random bytes with os.urandom, so a better example would be:
from hashlib import pbkdf2_hmac
from os import urandom
salt = urandom(16)
key = pbkdf2_hmac('sha1', b"somekeyvalue", salt, 12345, 32)
You may also want to increase the number of iterations (I think the recommended minimum number is 10,000).
The rest of the code is easy to 'translate'.
For the timestamp, use time.time to get the current time and multiply by 1000.
import time
milliseconds = str(round(time.time() * 1000))
For encoding you can use base64.urlsafe_b64encode (it includes padding, but you could remove it with .rstrip(b'=')).
Now, for the encryption part, Python doesn't have a built-in encryption module, so you'll have to use a third party library. I recommend pycryptodome or cryptography.
At this point I must warn you that the AES mode you're using is very weak. Please consider using CBC or CTR, or better yet use an authenticated encryption algorithm.
Related
I was trying to convert a java code to python. tried to use pycryptodome library for the purpose. This is java code:
try {
String data= "shouldEncryptDATA";
String bas64ed= "";
int len12 = Integer.parseInt("12");
byte[] randomparam= new byte[len12];
new SecureRandom().nextBytes(randomparam);
SecretKeySpec secretKeySpec= new SecretKeySpec("1f1f7892GKG38815".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(1, secretKeySpec, new GCMParameterSpec(Integer.parseInt("16") * 8, randomparam));
byte[] bytedata= cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
byte[] newbytearray= new byte[bytedata.length + len12];
System.arraycopy(randomparam, 0, newbytearray, 0, len12);
System.arraycopy(bytedata, 0, newbytearray, len12, bytedata.length);
bas64ed= Base64.getEncoder().encodeToString(newbytearray);
System.out.println("bas64ed: "+bas64ed);
System.out.println(URLEncoder.encode(bas64ed, "UTF-8"));
} catch (Exception unused_ex) {
System.out.println();
System.out.println("ERROR: " + unused_ex);
}
so far I tried below python code to mimic above java code:
import base64
from Crypto.Cipher import AES
import urllib.parse
from Crypto.Random import get_random_bytes
data= "shouldEncryptDATA"
key = b '1f1f7892GKG38815'
len12 = 12
v1 = bytearray(len12)
cipher = AES.new(key, AES.MODE_GCM, nonce=get_random_bytes(12) )
ciphertext, tag = cipher.encrypt_and_digest(data.encode("utf8"))
base64ed = ""
for x in cipher.nonce, ciphertext, tag:
base64ed += base64.b64encode(x).decode('utf-8')
urlencoded = urllib.parse.quote_plus(base64ed+"\n")
print(urlencoded )
the above python code generates an output but the problem is that when I supply the output to the decryption code I got MAC check Failed. so I realized my implementation in the python code is somewhat is the problem because the output of the Java code when passed to the decryption code, it works and decrypts the data. so How can I correctly convert the Java encryption code to Python? using the Pycryptodome or any other suitable library.
The bug in the Python code is in the concatenation, it must be:
base64ed = base64.b64encode(cipher.nonce + ciphertext + tag).decode('utf-8')
After this fix, if you compare the ciphertexts of both codes with identical input data (especially with identical IV), they are the same.
Small question regarding a TOTP generation algorithm please.
I am building a TOTP generation algorithm. In order to do so, I am using HMAC SHA1.
The result is correct, I used many time this HMAC SHA1 generated TOTP to authenticate myself to servers, I had confirmation the TOTP is correct, very happy.
Now, knowing HMAC SHA1 is a bit less secure, I would like to migrate from HMAC SHA1 to HMAC SHA256.
I thought I was as simple as changing the HMAC algorithm. Unfortunately, all TOTP generated with HMAC SHA256 were not accepted by the server.
Just to emphasize, this question is about how to make it work with HMAC SHA256.
This question is not about:
how secure is HMAC SHA1
if it is a good choice to migrate from HMAC SHA1 to HMAC256
how to change the server to accept HMAC256 generated TOTP.
This technical question is really about a technical algorithm of TOTP generation with HAMC SHA256.
The code I use to generate HMAC SHA1 TOTP is:
String getTOTP() {
try {
long value = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() / TimeUnit.SECONDS.toMillis(30);
final byte[] key = new Base32().decode("the_password".toUpperCase(Locale.US));
final var data = new byte[8];
for (int i = 8; i-- > 0; value >>>= 8) {
data[i] = (byte) value;
}
final var signKey = new SecretKeySpec(key, "HmacSHA1"); // would like to change here to "HmacSHA256"
final var mac = Mac.getInstance("HmacSHA1"); // would like to change here to "HmacSHA256"
mac.init(signKey);
final String hashString = new String(new Hex().encode(mac.doFinal(data)));
final var offset = Integer.parseInt(hashString.substring(hashString.length() - 1), 16);
final var truncatedHash = hashString.substring(offset * 2, offset * 2 + 8);
final var finalHash = String.valueOf(Integer.parseUnsignedInt(truncatedHash, 16) & 0x7FFFFFFF);
final var finalHashCut = finalHash.substring(finalHash.length() - 6);
System.out.println("THE TOTP generated with HmacSHA1 is " + finalHashCut);
System.out.println("THE TOTP generated with HmacSHA256 will not work though :'( ");
return finalHashCut;
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
LOGGER.warn("", e);
return "";
}
}
Question: What element of the algorithm should I adjust in order to have the TOTP using HMAC SHA256, and still work please?
Thank you.
Thanks to Robert's comment, changing the hash will change the output.
Unless the destination server accepts the other hash algorithm, changing hash on the client will yield a different TOTP, and will not be accepted by the server.
I am working on a project that features hand-rolled password hashing in Java, and am tasked with duplicating the hashing scheme in NodeJS in order to prevent users from having to reset their passwords.
I cannot seem to write a hashing function that generates an identical key to the one generated by the legacy code.
I've done some digging into the internals of each implementation, and I've identified a key difference in that the NodeJS version measures keyLength in bytes and the Java version uses bits. I've taken this into account with my current attempt, but am still having no luck.
The Java implementation:
public static String hash(byte[] salt, String password) throws HashException {
try {
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
return encode(f.generateSecret(spec).getEncoded());
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new HashException("Issues hashing the password", e);
}
}
My attempt in NodeJS:
const crypto = require("crypto")
const encodedsalt = "oFoz+aZuGX5Ad/HAa35HLw=="
const encodedpass = "fp+gXqcO7fBKuXuhZQPY/A=="
// decode
const decodedsalt = Buffer.from(encodedsalt, "base64")
const decodedpass = Buffer.from(encodedpass, "base64")
var key = crypto.pbkdf2Sync("13111789", decodedsalt, 65536, 16, "sha1")
var encodedKey = key.toString("base64")
I expect the keys to match, because each implementation uses an identical byte array for the salt, and an identical string for the password. Instead, the keys match in length only. I'm certain that there is some quirk of the implementations that I am not noticing, but I can't see it! Any help appreciated - all other PBKDF2 questions focused on some other minor issue, so I'm at a loss here.
the code you posted is basically fine. you've not defined encode in your Java code, so the problem might be there. here's the Java code for a working example:
import java.util.Base64;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
public static String hash(byte[] salt, String password) throws Exception {
SecretKeyFactory pbkdf2 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
byte[] secret = pbkdf2.generateSecret(spec).getEncoded();
return Base64.getEncoder().encodeToString(secret);
}
this can be run as:
String base64_salt = "oFoz+aZuGX5Ad/HAa35HLw==";
byte[] salt = Base64.getDecoder().decode(base64_salt);
System.out.println(hash(salt, "13111789"));
giving me: On7VbkKDXdz8aTWo5tDBXw==
the equivalent JS code would be:
const crypto = require("crypto");
function hash(salt, password) {
const key = crypto.pbkdf2Sync(password, salt, 65536, 16, "sha1");
return key.toString("base64");
}
which can be run as:
const base64_salt = "oFoz+aZuGX5Ad/HAa35HLw==";
const salt = Buffer.from(base64_salt, "base64");
console.log(hash(salt, "13111789"));
also giving me: On7VbkKDXdz8aTWo5tDBXw==
I'm only asking this because I have read many posts for 2 days now about crypto AES encryption, and just when I thought I was getting it, I realized I wasn't getting it at all.
This post is the closest one to my issue, I have exactly the same problem but it is unanswered:
CryptoJS AES encryption and JAVA AES decryption value mismatch
I have tried doing it in many ways but I haven't gotten it right.
First Off
I'm getting the already encrypted string (I only got the code to see how they were doing it), so modifying the encryption way is not an option. That's why all the similar questions aren't that useful to me.
Second
I do have access to the secret key and I can modify it (so adjusting length is an option if neccessary).
The encryption is done on CryptoJS and they send the encrypted string as a GET parameter.
GetParamsForAppUrl.prototype.generateUrlParams = function() {
const self = this;
return new Promise((resolve, reject) => {
const currentDateInMilliseconds = new Date().getTime();
const secret = tokenSecret.secret;
var encrypted = CryptoJS.AES.encrypt(self.authorization, secret);
encrypted = encrypted.toString();
self.urlParams = {
token: encrypted,
time: currentDateInMilliseconds
};
resolve();
});
};
I can easily decrypt this on javascript using CryptoJS with:
var decrypted = CryptoJS.AES.decrypt(encrypted_string, secret);
console.log(decrypted.toString(CryptoJS.enc.Utf8));
But I don't want to do this on Javascript, for security reasons, so I'm trying to decrypt this on Java:
String secret = "secret";
byte[] cipherText = encrypted_string.getBytes("UTF8");
SecretKey secKey = new SecretKeySpec(secret.getBytes(), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
String myDecryptedText = = new String(bytePlainText);
Before I had any idea of what I was doing, I tried base64 decoding, adding some IV and a lot of stuff I read, of course none of it worked.
But after I started to understand, kinda, what I was doing, I wrote that simple script above, and got me the same error on the post: Invalid AES key length
I don't know where to go from here. After reading a lot about this, the solution seems to be hashing or padding, but I have no control on the encryption method, so I can't really hash the secret or pad it.
But as I said, I can change the secret key so it can match some specific length, and I have tried changing it, but as I'm shooting in the dark here, I don't really know if this is the solution.
So, my question basically is, If I got the encrypted string (in javascript like the first script) and the secret key, is there a way to decrypt it (in Java)? If so, how to do it?
Disclaimer: Do not use encryption unless you understand encryption concepts including chaining mode, key derivation functions, IV and block size. And don't roll your own security scheme but stick to an established one. Just throwing in encryption algorithms doesn't mean an application has become any more secure.
CryptoJS implements the same key derivation function as OpenSSL and the same format to put the IV into the encrypted data. So all Java code that deals with OpenSSL encoded data applies.
Given the following Javascript code:
var text = "The quick brown fox jumps over the lazy dog. 👻 👻";
var secret = "René Über";
var encrypted = CryptoJS.AES.encrypt(text, secret);
encrypted = encrypted.toString();
console.log("Cipher text: " + encrypted);
We get the cipher text:
U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=
On the Java side, we have
String secret = "René Über";
String cipherText = "U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=";
byte[] cipherData = Base64.getDecoder().decode(cipherText);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);
System.out.println(decryptedText);
The result is:
The quick brown fox jumps over the lazy dog. 👻 👻
That's the text we started with. And emojis, accents and umlauts work as well.
GenerateKeyAndIV is a helper function that reimplements OpenSSL's key derivation function EVP_BytesToKey (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
/**
* Generates a key and an initialization vector (IV) with the given salt and password.
* <p>
* This method is equivalent to OpenSSL's EVP_BytesToKey function
* (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
* By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
* </p>
* #param keyLength the length of the generated key (in bytes)
* #param ivLength the length of the generated IV (in bytes)
* #param iterations the number of digestion rounds
* #param salt the salt data (8 bytes of data or <code>null</code>)
* #param password the password data (optional)
* #param md the message digest algorithm to use
* #return an two-element array with the generated key and IV
*/
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
md.reset();
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
md.update(password);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
}
generatedLength += digestLength;
}
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte)0);
}
}
Note that you have to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy. Otherwise, AES with key size of 256 won't work and throw an exception:
java.security.InvalidKeyException: Illegal key size
Update
I have replaced Ola Bini's Java code of EVP_BytesToKey, which I used in the first version of my answer, with a more idiomatic and easier to understand Java code (see above).
Also see How to decrypt file in Java encrypted with openssl command using AES?.
When encrypting on one system and decrypting on another you are at the mercy of system defaults. If any system defaults do not match (and they often don't) then your decryption will fail.
Everything has to be byte for byte the same on both sides. Effectively that means specifying everything on both sides rather than relying on defaults. You can only use defaults if you are using the same system at both ends. Even then, it is better to specify exactly.
Key, IV, encryption mode, padding and string to bytes conversion all need to be the same at both ends. It is especially worth checking that the key bytes are the same. If you are using a Key Derivation Function (KDF) to generate your key, then all the parameters for that need to be the same, and hence specified exactly.
Your "Invalid AES key length" may well indicate a problem with generating your key. You use getBytes(). That is probably an error. You need to specify what sort of bytes you are getting: ANSI, UTF-8, EBCDIC, whatever. The default assumption for the string to byte conversion is the likely cause of this problem. Specify the conversion to be used explicitly at both ends. That way you can be sure that they match.
Crypto is designed to fail if the parameters do not match exactly for encryption and decryption. For example, even a one bit difference in the key will cause it to fail.
The Openbravo software and its derivatives (e.g. unicentaopos) have the following implementation of encryption to store the database password in a plain configuration file.
package com.openbravo.pos.util;
import java.io.UnsupportedEncodingException;
import java.security.*;
import javax.crypto.*;
/**
*
* #author JG uniCenta
*/
public class AltEncrypter {
private Cipher cipherDecrypt;
private Cipher cipherEncrypt;
/** Creates a new instance of Encrypter
* #param passPhrase */
public AltEncrypter(String passPhrase) {
try {
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(passPhrase.getBytes("UTF8"));
KeyGenerator kGen = KeyGenerator.getInstance("DESEDE");
kGen.init(168, sr);
Key key = kGen.generateKey();
cipherEncrypt = Cipher.getInstance("DESEDE/ECB/PKCS5Padding");
cipherEncrypt.init(Cipher.ENCRYPT_MODE, key);
cipherDecrypt = Cipher.getInstance("DESEDE/ECB/PKCS5Padding");
cipherDecrypt.init(Cipher.DECRYPT_MODE, key);
} catch (UnsupportedEncodingException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
}
}
/**
*
* #param str
* #return
*/
public String encrypt(String str) {
try {
return StringUtils.byte2hex(cipherEncrypt.doFinal(str.getBytes("UTF8")));
} catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException e) {
}
return null;
}
/**
*
* #param str
* #return
*/
public String decrypt(String str) {
try {
return new String(cipherDecrypt.doFinal(StringUtils.hex2byte(str)), "UTF8");
} catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException e) {
}
return null;
}
}
To encrypt, the following is used (only the password is encrypted):
config.setProperty("db.user", jtxtDbUser.getText());
AltEncrypter cypher = new AltEncrypter("cypherkey" + jtxtDbUser.getText());
config.setProperty("db.password", "crypt:" + cypher.encrypt(new String(jtxtDbPassword.getPassword())));
To decrypt, the following is used:
String sDBUser = m_App.getProperties().getProperty("db.user");
String sDBPassword = m_App.getProperties().getProperty("db.password");
if (sDBUser != null && sDBPassword != null && sDBPassword.startsWith("crypt:")) {
AltEncrypter cypher = new AltEncrypter("cypherkey" + sDBUser);
sDBPassword = cypher.decrypt(sDBPassword.substring(6));
}
I am working on an independent software module in C# and I'd like to read the database password from that configuration file. Any advice on how to accomplish this?
From analyzing the code, I can deduce that:
The password "encryption" is reversible because it is later used in the software to build database connection strings.
The base passphrase is "cypherkey" + username
The password is stored in the plain file with the format
db.password=crypt:XXX
where XXX is the encrypted password.
Please help me to work out how to decrypt the password. Help on actually reading the plain file is not necessary. Please assume that I already have stored the username and encrypted password (without the "crypt:" part) in variables in the C# program.
I've been trying to modify the existing examples on similar question but they focus on AES and so far I have not been successful with this.
Basically, the following function in C# should be built:
private string DecryptPassword(string username, string encryptedPassword)
How would I do this?
The software is open source and can be found here
One test case: DecryptPassword("mark", "19215E9576DE6A96D5F03FE1D3073DCC") should return the password getmeback. The base passphrase would be cypherkeymark. I have tested in different machines and the "hashed" password is always the same using the same username.
The method used by AltEncrypter to derive a key from the password is terrible. This approach should not be used.
First of all, it's not secure. A key derivation algorithm is not secure unless it is computationally intensive. Instead, use an algorithm like scrypt, bcrypt, or PBKDF2.
Second, the SHA1PRNG algorithm is not well defined. Saying, "it uses SHA-1" isn't sufficient. How often is a hash performed? It's not standardized; you won't be able to request a "SHA1PRNG" on another platform (like .Net), and get the same output.
So, scrap this encryption method and use something easy and secure, written and maintained by knowledgeable people.
Unfortunately, the problems don't end there. The AltEncrypter utility is used in the worst way possible, with a key that isn't secret, to reversibly encrypt an authentication password. This is not secure at all. It allows an attacker to decrypt user passwords and use them against the user's accounts on other systems.
It's almost like the author of this system wanted to create a security catastrophe.
This is a note. I cannot add comments but I think that the algorithm used to encrypt is not SHA1. It is "DESEDE/ECB/PKCS5Padding" look at the line where the cipher to encrypt is created (obtained)
cipherEncrypt = Cipher.getInstance("DESEDE/ECB/PKCS5Padding");
SHA1PRNG is a pseudo-random number generator used to generate a first random number used into the encryption process in order to generate "different" encryptions even when the same plain text is encrypted.
Another important thing is the key used to encrypt, I mean:
KeyGenerator kGen = KeyGenerator.getInstance("DESEDE");
kGen.init(168, sr);
Key key = kGen.generateKey(); <-- this key
this key is used to encrypt and decrypt but I cannot see where it is stored. I mean that it is regenerated every time. It should be stored and retrieved from somewhere and not regenerated because, it is not possible to decrypt any cipher text if it is not used the same key.
This is an answer using some workarounds.
I've tried reimplementing the SHA1PRNG provided the GNU implementation (which is opensource), but it doesn't give the same results as the properitary SUN implementation (so either they're different or I have implemented it in a wrong way). So I've implemented a workaround: Call a java-program to derive the key for us. Yes, this is very cheap, but a working work-around for the time being. If someone sees the mistake in my SHA1PRNG implementation, let me know.
So first, here's a simple Java program which will derive a 168-bit key given a seed using the SHA1PRNG generator. Simply outputs it on stdout, space seperated.
import java.io.UnsupportedEncodingException;
import java.security.*;
import javax.crypto.*;
public class PasswordDeriver {
public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException {
if(args.length == 0){
System.out.println("You need to give the seed as the first argument.");
return;
}
//Use Java to generate the key used for encryption and decryption.
String passPhrase = args[args.length-1];
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(passPhrase.getBytes("UTF8"));
KeyGenerator kGen = KeyGenerator.getInstance("DESEDE");
kGen.init(168, sr);
Key key = kGen.generateKey();
//Key is generated, now output it.
//System.out.println("Format: " + key.getFormat());
byte[] k = key.getEncoded();
for(int i=0; i < k.length; i++){
System.out.print(String.format((i == k.length - 1) ? "%X" : "%X ", k[i]));
}
}
}
This is saved as PasswordDeriver.java, compiled using javac <file> and the resulting PasswordDeriver.class is then placed in the same folder as this compiled program: (The actual C# program)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.IO;
using System.Diagnostics;
namespace OpenbravoDecrypter
{
class Program
{
static void Main(string[] args)
{
var decrypted = Decrypt("19215E9576DE6A96D5F03FE1D3073DCC", "mark");
Console.ReadLine();
}
static string Decrypt(string ciphertext, string username)
{
//Ciphertext is given as a hex string, convert it back to bytes
if(ciphertext.Length % 2 == 1) ciphertext = "0" + ciphertext; //pad a zero left is necessary
byte[] ciphertext_bytes = new byte[ciphertext.Length / 2];
for(int i=0; i < ciphertext.Length; i+=2)
ciphertext_bytes[i / 2] = Convert.ToByte(ciphertext.Substring(i, 2), 16);
//Get an instance of a tripple-des descryption
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
tdes.Mode = CipherMode.ECB; //ECB as Cipher Mode
tdes.Padding = PaddingMode.PKCS7; //PKCS7 padding (same as PKCS5, good enough)
byte[] key_bytes = DeriveKeyWorkAround(username);
Console.WriteLine("Derived Key: " + BitConverter.ToString(key_bytes));
//Start the decryption, give it the key, and null for the IV.
var decryptor = tdes.CreateDecryptor(key_bytes, null);
//Decrypt it.
var plain = decryptor.TransformFinalBlock(ciphertext_bytes, 0, ciphertext_bytes.Length);
//Output the result as hex string and as UTF8 encoded string
Console.WriteLine("Plaintext Bytes: " + BitConverter.ToString(plain));
var s = Encoding.UTF8.GetString(plain);
Console.WriteLine("Plaintext UTF-8: " + s);
return s;
}
/* Work around the fact that we don't have a C# implementation of SHA1PRNG by calling into a custom-prepared java file..*/
static byte[] DeriveKeyWorkAround(string username)
{
username = "cypherkey" + username;
string procOutput = "";
//Invoke java on our file
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c java PasswordDeriver \"" + username + "\"";
p.StartInfo.RedirectStandardOutput = true;
p.OutputDataReceived += (e, d) => procOutput += d.Data;
p.StartInfo.UseShellExecute = false;
p.Start();
p.BeginOutputReadLine();
p.WaitForExit();
//Convert it back
byte[] key = procOutput.Split(' ').Select(hex => Convert.ToByte(hex, 16)).ToArray();
return key;
}
/* This function copies the functionality of the GNU Implementation of SHA1PRNG.
* Currently, it's broken, meaning that it doesn't produce the same output as the SUN implenetation of SHA1PRNG.
* Case 1: the GNU implementation is the same as the SUN implementation, and this re-implementation is just wrong somewhere
* Case 2: the GNU implementation is not the same the SUN implementation, therefore you'd need to reverse engineer some existing
* SUN implementation and correct this method.
*/
static byte[] DeriveKey(string username)
{
//adjust
username = "cypherkey" + username;
byte[] user = Encoding.UTF8.GetBytes(username);
//Do SHA1 magic
var sha1 = new SHA1CryptoServiceProvider();
var seed = new byte[20];
byte[] data = new byte[40];
int seedpos = 0;
int datapos = 0;
//init stuff
byte[] digestdata;
digestdata = sha1.ComputeHash(data);
Array.Copy(digestdata, 0, data, 0, 20);
/* seeding part */
for (int i=0; i < user.Length; i++)
{
seed[seedpos++ % 20] ^= user[i];
}
seedpos %= 20;
/* Generate output bytes */
byte[] bytes = new byte[24]; //we need 24 bytes (= 192 bit / 8)
int loc = 0;
while (loc < bytes.Length)
{
int copy = Math.Min(bytes.Length - loc, 20 - datapos);
if (copy > 0)
{
Array.Copy(data, datapos, bytes, loc, copy);
datapos += copy;
loc += copy;
}
else
{
// No data ready for copying, so refill our buffer.
Array.Copy(seed, 0, data, 20, 20);
byte[] digestdata2 = sha1.ComputeHash(data);
Array.Copy(digestdata2, 0, data, 0, 20);
datapos = 0;
}
}
Console.WriteLine("GENERATED KEY:\n");
for(int i=0; i < bytes.Length; i++)
{
Console.Write(bytes[i].ToString("X").PadLeft(2, '0'));
}
return bytes;
}
}
}
You can see the standard stuff such as initializing a tripple-DES cryptoprovider, giving it a key and computing the decryption of the ciphertext in there. It also contains the currently broken implementation of the SHA1PRNG and the workaround. Given that java is in the PATH of the current environment variable, this program produces the output:
Derived Key: 86-EF-C1-F2-2F-97-D3-F1-34-49-23-89-E3-EC-29-80-02-92-52-40-49-5D-CD-C1
Plaintext Bytes: 67-65-74-6D-65-62-61-63-6B
Plaintext UTF-8: getmeback
So, here you have the decrypt function (encrypting it would the same, just change .CreateDecryptor() to .CreateEncryptor()). If you forget about the code doing the key derivation, the decryption code does its work in only ~20 lines of code. So in review, my answer is a starting point for others who want to make this solution 100% C#. Hope this helps.