I need to replicate the functionality of the following JAVA code that receives a string with the exponent and modulus of a public key to generate a public key with said parameters and encrypt a string:
package snippet;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.Security;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;
import javax.crypto.Cipher;
public class Snippet {
public static void main(String ... strings) {
try {
// Needed if you don't have this provider
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
//String and received public key example
String ReceivedString = "1234";
String publicRSA = "010001|0097152d7034a8b48383d3dba20c43d049";
EncryptFunc(ReceivedString, publicRSA);
//The result obtained from the ReceivedString and the publicRSA is as follows:
//Result in hex [1234] -> [777786fe162598689a8dc172ed9418cb]
} catch (Exception ex) {
System.out.println("Error: " );
ex.printStackTrace();
}
}
public static String EncryptFunc(String ReceivedString, String clavePublica) throws Exception {
String result = "";
//We separate the received public string into exponent and modulus
//We receive it as "exponent|modulus"
String[] SplitKey = clavePublica.split("\\|");
KeyFactory keyFactory = KeyFactory.getInstance("RSA","BC");
RSAPublicKeySpec ks = new RSAPublicKeySpec(new BigInteger(hex2byte(SplitKey[1])), new BigInteger(hex2byte(SplitKey[0])));
//With these specs, we generate the public key
RSAPublicKey pubKey = (RSAPublicKey)keyFactory.generatePublic(ks);
//We instantiate the cypher, with the EncryptFunc and the obtained public key
Cipher cipher= Cipher.getInstance("RSA/None/NoPadding","BC");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
//We reverse the ReceivedString and encrypt it
String ReceivedStringReverse = reverse(ReceivedString);
byte[] cipherText2 = cipher.doFinal(ReceivedStringReverse.getBytes("UTF8"));
result = byte2hex(cipherText2);
System.out.println("result in hex ["+ReceivedString+"] -> ["+result+"]");
return result;
}
public static byte[] hex2byte(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
public static String byte2hex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte aByte : bytes) {
result.append(String.format("%02x", aByte));
// upper case
// result.append(String.format("%02X", aByte));
}
return result.toString();
}
public static String reverse(String source) {
int i, len = source.length();
StringBuilder dest = new StringBuilder(len);
for (i = (len - 1); i >= 0; i--){
dest.append(source.charAt(i));
}
return dest.toString();
}
}
I've tried several approaches with this one, And I have done some searching here, here, here, here and here.
I Managed to create the public key with the given parameters, but the results are always different when I encrypt the string:
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace RSACypherTest
{
public class Program
{
public static RSACryptoServiceProvider rsa;
static void Main(string[] args)
{
string str = "1234";
string publicRSA = "010001|0097152d7034a8b48383d3dba20c43d049";
string encrypted = "";
Console.WriteLine("Original text: " + str);
encrypted = Encrypt(str, publicRSA);
Console.WriteLine("Encrypted text: " + encrypted);
Console.ReadLine();
}
public static string Encrypt(string str, string PublicRSA)
{
string[] Separated = PublicRSA.Split('|');
RsaKeyParameters pubParameters = MakeKey(Separated[1], Separated[0], false);
IAsymmetricBlockCipher eng = new Pkcs1Encoding(new RsaEngine());
eng.Init(true, pubParameters);
byte[] plaintext = Encoding.UTF8.GetBytes(Reverse(str));
byte[] encdata = eng.ProcessBlock(plaintext, 0, plaintext.Length);
return ByteArrayToString(encdata);
}
public static string Reverse(string s)
{
char[] charArray = s.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-", "");
}
public static byte[] StringToByteArray(string hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
private static RsaKeyParameters MakeKey(string modulusHexString, string exponentHexString, bool isPrivateKey)
{
var modulus = new BigInteger(modulusHexString, 16);
var exponent = new BigInteger(exponentHexString, 16);
return new RsaKeyParameters(isPrivateKey, modulus, exponent);
}
}
}
I'm trying to use BouncyCastle because it seems to be the most effcient way of dealing with the key generation and everything. Any help concerning this would be very much appreciated.
Thanks in advance.
This is not the answer to your question but may help you in understanding RSA encryption.
I setup a sample encryption program in C# and used your given public key (converted the BigInteger modulus & exponent to Base64 values and further just wrote the XML-String representation of the public to use this key for encryption. The keylength is good for a length of maximum 5 byte data.
When running the encryption 5 times you will receive different encodedData (here in Base64 encoding) each run. So it's the expected behavior of the RSA encryption.
As C# allows me to "build" a short key it is not possible to generate a fresh keypair of such length and I doubt that Bouncy Castle would do (but here on SO there are many colleagues with a much better understanding of BC :-).
If you would like the program you can use the following external link to the program: https://jdoodle.com/ia/40.
Result:
load a pre created public key
publicKeyXML2: lxUtcDSotIOD09uiDEPQSQ==AQAB
encryptedData in Base64: JIFfO7HXCvdi0nSxKb0eLA==
encryptedData in Base64: dvtRw0U0KtT/pDJZW2X0FA==
encryptedData in Base64: CqJJKZevO6jWH6DQ1dnkhQ==
encryptedData in Base64: G7cL6BBwxysItvD/Rg0PuA==
encryptedData in Base64: HcfZJITu/PzN84WgI8yc6g==
code:
using System;
using System.Security.Cryptography;
using System.Text;
class RSACSPSample
{
static void Main()
{
try
{
//Create byte arrays to hold original, encrypted, and decrypted data.
byte[] dataToEncrypt = System.Text.Encoding.UTF8.GetBytes("1234");
byte[] encryptedData;
//Create a new instance of RSACryptoServiceProvider to generate
//public and private key data.
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
Console.WriteLine("load a pre created public key");
string publicKeyXML = "<RSAKeyValue><Modulus>AJcVLXA0qLSDg9PbogxD0Ek=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
RSA.FromXmlString(publicKeyXML);
string publicKeyXML2 = RSA.ToXmlString(false);
Console.WriteLine("publicKeyXML2: " + publicKeyXML2);
Console.WriteLine();
//Pass the data to ENCRYPT, the public key information
//(using RSACryptoServiceProvider.ExportParameters(false),
//and a boolean flag specifying no OAEP padding.
for (int i = 0; i < 5; i++)
{
encryptedData = RSAEncrypt(dataToEncrypt, RSA.ExportParameters(false), false);
string encryptedDataBase64 = Convert.ToBase64String(encryptedData);
Console.WriteLine("encryptedData in Base64: " + encryptedDataBase64);
}
}
}
catch (ArgumentNullException)
{
//Catch this exception in case the encryption did
//not succeed.
Console.WriteLine("Encryption failed.");
}
}
public static byte[] RSAEncrypt(byte[] DataToEncrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
{
try
{
byte[] encryptedData;
//Create a new instance of RSACryptoServiceProvider.
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
{
//Import the RSA Key information. This only needs
//toinclude the public key information.
RSA.ImportParameters(RSAKeyInfo);
//Encrypt the passed byte array and specify OAEP padding.
//OAEP padding is only available on Microsoft Windows XP or
//later.
encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding);
}
return encryptedData;
}
//Catch and display a CryptographicException
//to the console.
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
return null;
}
}
}
While I won't mark my own answer as the correct one, I've found that there's the possibility to recreate the entire functionality of the java code mentioned in my question.
As Michael Fehr mentions in his answer, Its absolutely logical that any encryption method will try to avoid creating repeating or predictable patterns, as this answer perfectly describes.
Since in this particular situation the aim is to replicate the java code functionality, and said functionality revolves around getting the same results when encrypting a string with a given public key, we can use the answer in this post to generate a pice of code like the following:
private static string EncryptMessage(string str, string publicRSA)
{
string[] Separated = publicRSA.Split('|');
RsaKeyParameters pubParameters = MakeKey(Separated[1], Separated[0], false);
var eng = new RsaEngine();
eng.Init(true, pubParameters);
string x = Reverse(str);
byte[] plaintext = Encoding.UTF8.GetBytes(x);
var encdata = ByteArrayToString(eng.ProcessBlock(plaintext, 0, plaintext.Length));
return encdata;
}
private static RsaKeyParameters MakeKey(string modulusHexString, string exponentHexString, bool isPrivateKey)
{
byte[] mod = StringToByteArray(modulusHexString);
byte[] exp = StringToByteArray(exponentHexString);
var modulus = new BigInteger(mod);
var exponent = new BigInteger(exp);
return new RsaKeyParameters(isPrivateKey, modulus, exponent);
}
To recap:
As Michael Fehr says, it is not only normal but expected of a crypyography engine to NOT generate repeatable/predictable patterns
To deliver on the previous point, they add random "padding" to the messages
It's possible (but not recommended) to use BouncyCastle to generate a No-padding engine, emulating the functionality of Java code such as this Cipher rsa = Cipher.getInstance("RSA/ECB/nopadding");
Related
I'm trying to validate the content of an XML node with SHA-1 , basically, we generate an SHA-1 hash with the content of that node and both sides (client C# and server Java) should have exactly the same hash.
The problem is , I have checked with a diff tool the content of both texts and there is not any difference. But I'm getting a different hash than the client.
C# hash : 60-53-58-69-29-EB-53-BD-85-31-79-28-A0-F9-42-B6-DE-1B-A6-0A
Java hash: E79D7E6F2A6F5D776447714D896D4C3A0CBC793
The way the client (C#) is generating the hash is this:
try
{
Byte[] stream = null;
using (System.Security.Cryptography.SHA1CryptoServiceProvider shaProvider = new System.Security.Cryptography.SHA1CryptoServiceProvider())
{
stream = shaProvider.ComputeHash(System.Text.Encoding.UTF8.GetBytes(text));
if (stream == null)
{
hash = "Error";
}
else
{
hash = System.BitConverter.ToString(stream);
}
}
}
catch (Exception error)
{
hash = string.Format("Error SHA-1: {0}", error);
}
return hash;
and this is how the server (Java) is generating the hash:
byte[] key = content.getBytes();
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] hash = md.digest(key);
String result = "";
for (byte b : hash) {
result += Integer.toHexString(b & 255);
}
return result.toUpperCase();
can someone help me ? .. thanks :)
UPDATE:
In order to check what's going on I have checked other ways to get a SHA1 hash in C# and I found this:
/// <summary>
/// Compute hash for string encoded as UTF8
/// </summary>
/// <param name="s">String to be hashed</param>
/// <returns>40-character hex string</returns>
public static string SHA1HashStringForUTF8String(string s)
{
byte[] bytes = Encoding.UTF8.GetBytes(s);
using (var sha1 = SHA1.Create())
{
byte[] hashBytes = sha1.ComputeHash(bytes);
return System.BitConverter.ToString(hashBytes).Replace("-",string.Empty);
}
}
This code gives this output:
E79D07E6F2A6F5D776447714D896D4C3A0CBC793
AND !! I just noticed that Python is giving the same output (sorry, I should double checked this)
So this is the deal
Using this provider: System.Security.Cryptography.SHA1CryptoServiceProvider shaProvider = new System.Security.Cryptography.SHA1CryptoServiceProvider()
Is giving a completly different output on three different machines ..
Using the above method in C# gives the same result as python does, also, for some reason Java is giving a sightly different output:
E79D7E6F2A6F5D776447714D896D4C3A0CBC793
Ideas?, is java the problem? the byte to hex method on java is the problem? there is another alternative?
Try using this as your hashing in C#:
static string Hash(string input)
{
using (SHA1Managed sha1 = new SHA1Managed())
{
var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(input));
var sb = new StringBuilder(hash.Length * 2);
foreach (byte b in hash)
{
// can be "x2" if you want lowercase
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
}
Hash("test"); //a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
And then use this as your Java hashing:
private static String convertToHex(byte[] data) {
StringBuilder buf = new StringBuilder();
for (byte b : data) {
int halfbyte = (b >>> 4) & 0x0F;
int two_halfs = 0;
do {
buf.append((0 <= halfbyte) && (halfbyte <= 9) ? (char) ('0' + halfbyte) : (char) ('a' + (halfbyte - 10)));
halfbyte = b & 0x0F;
} while (two_halfs++ < 1);
}
return buf.toString();
}
public static String SHA1(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] textBytes = text.getBytes("iso-8859-1");
md.update(textBytes, 0, textBytes.length);
byte[] sha1hash = md.digest();
return convertToHex(sha1hash);
}
SHA1("test"); //a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
Note you need the following imports:
import java.io.UnsupportedEncodingException; import
java.security.MessageDigest; import
java.security.NoSuchAlgorithmException;
Throws declarations are option, adjust to best fit your code!
Your problem is that you're not hashing the same bytes in both API.
If you choose to modify java's version, it should look like this:
byte[] key = content.getBytes("UTF8");
[...]
If you choose to modify c#' version, it should look like this:
stream = shaProvider.ComputeHash(System.Text.Encoding.UTF16.GetBytes(text));
[...]
Either way, both api should get the key's bytes through the same encoding.
I'm attempting with little success to port over Google's code to generate a secure token for their captcha (https://github.com/google/recaptcha-java/blob/master/appengine/src/main/java/com/google/recaptcha/STokenUtils.java):
The original utility has the following:
private static final String CIPHER_INSTANCE_NAME = "AES/ECB/PKCS5Padding";
private static String encryptAes(String input, String siteSecret) {
try {
SecretKeySpec secretKey = getKey(siteSecret);
Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE_NAME);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return BaseEncoding.base64Url().omitPadding().encode(cipher.doFinal(input.getBytes("UTF-8")));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static SecretKeySpec getKey(String siteSecret){
try {
byte[] key = siteSecret.getBytes("UTF-8");
key = Arrays.copyOf(MessageDigest.getInstance("SHA").digest(key), 16);
return new SecretKeySpec(key, "AES");
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
public static void main(String [] args) throws Exception {
//Hard coded the following to get a repeatable result
String siteSecret = "12345678";
String jsonToken = "{'session_id':'abf52ca5-9d87-4061-b109-334abb7e637a','ts_ms':1445705791480}";
System.out.println(" json token: " + jsonToken);
System.out.println(" siteSecret: " + siteSecret);
System.out.println(" Encrypted stoken: " + encryptAes(jsonToken, siteSecret));
Given the values I hardcoded, I get Irez-rWkCEqnsiRLWfol0IXQu1JPs3qL_G_9HfUViMG9u4XhffHqAyju6SRvMhFS86czHX9s1tbzd6B15r1vmY6s5S8odXT-ZE9A-y1lHns" back as my encrypted token.
My Java and crypto skills are more than a little rusty, and there aren't always direct analogs in C#. I attempted to merge encrypeAes() and getKey() with the following, which isn't correct:
public static string EncryptText(string PlainText, string siteSecret)
{
using (RijndaelManaged aes = new RijndaelManaged())
{
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
var bytes = Encoding.UTF8.GetBytes(siteSecret);
SHA1 sha1 = SHA1.Create();
var shaKey = sha1.ComputeHash(bytes);
byte[] targetArray = new byte[16];
Array.Copy(shaKey, targetArray, 16);
aes.Key = targetArray;
ICryptoTransform encrypto = aes.CreateEncryptor();
byte[] plainTextByte = ASCIIEncoding.UTF8.GetBytes(PlainText);
byte[] CipherText = encrypto.TransformFinalBlock(plainTextByte, 0, plainTextByte.Length);
return HttpServerUtility.UrlTokenEncode(CipherText); //Equivalent to java's BaseEncoding.base64Url()?
}
}
The C# version produces the incorrect value of: Ye+fySvneVUZJXth67+Si/e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ/430LgYcathLLd9U=
Your code almost works as expected. It's just that you somehow mixed up the outputs of the Java version (and possibly the C# version).
If I execute your Java code (JDK 7 & 8 with Guava 18.0), I get
Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U
and if I execute your C# code (DEMO), I get
Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U1
So, the C# version has an additional "1" at the end. It should be a padding character, but isn't. This means that HttpServerUtility.UrlTokenEncode() doesn't provide a standards conform URL-safe Base64 encoding and you shouldn't use it. See also this Q&A.
The URL-safe Base64 encoding can be easily derived from the normal Base64 encoding (compare tables 1 and 2 in RFC4648) as seen in this answer by Marc Gravell:
string returnValue = System.Convert.ToBase64String(toEncodeAsBytes)
.TrimEnd(padding).Replace('+', '-').Replace('/', '_');
with:
static readonly char[] padding = { '=' };
That's not all. If we take your Java output of
Ye+fySvneVUZJXth67+Si/e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ/430LgYcathLLd9U=
and decrypt it, then we get the following token:
{"session_id":"4182e173-3a24-4c10-b76c-b85a36be1173","ts_ms":1445786965574}
which is different from the token that you have in your code:
{'session_id':'abf52ca5-9d87-4061-b109-334abb7e637a','ts_ms':1445705791480}
The main remaining problem is that you're using invalid JSON. Strings and keys in JSON need to be wrapped in " and not '.
Which means that the encrypted token actually should have been (using a valid version of the token from your code):
D9rOP07fYgBfza5vbGsvdPe8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBsAWBDgtdSozv4jS_auBU-CgjlrJ_430LgYcathLLd9U
Here's a C# implementation that reproduces the same result as your Java code:
class Program
{
public static byte[] GetKey(string siteSecret)
{
byte[] key = Encoding.UTF8.GetBytes(siteSecret);
return SHA1.Create().ComputeHash(key).Take(16).ToArray();
}
public static string EncryptAes(string input, string siteSecret)
{
var key = GetKey(siteSecret);
using (var aes = AesManaged.Create())
{
if (aes == null) return null;
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
aes.Key = key;
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
var enc = aes.CreateEncryptor(key, new byte[16]);
return UrlSafeBase64(enc.TransformFinalBlock(inputBytes,0,input.Length));
}
}
// http://stackoverflow.com/a/26354677/162671
public static string UrlSafeBase64(byte[] bytes)
{
return Convert.ToBase64String(bytes).TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
}
static void Main(string[] args)
{
string siteSecret = "12345678";
string jsonToken = "{'session_id':'abf52ca5-9d87-4061-b109-334abb7e637a','ts_ms':1445705791480}";
Console.WriteLine(" json token: " + jsonToken);
Console.WriteLine(" siteSecret: " + siteSecret);
Console.WriteLine(EncryptAes(jsonToken, siteSecret));
Console.ReadLine();
}
}
I don't know why you said you're getting Irez-rWkCEqnsiRLWfol0IXQu1JPs3qL_G_9HfUViMG9u4XhffHqAyju6SRvMhFS86czHX9s1tbzd6B15r1vmY6s5S8odXT-ZE9A-y1lHns from the Java program because I'm not getting that output. The output I'm getting from both the C# version and the Java version is this:
Ye-fySvneVUZJXth67-Si_e8fBUV4Sxs7wEXVDEOJjBMHl1encvt65gGIj8CiFzBGp5uUgKYJZCuQ4rc964vZigjlrJ_430LgYcathLLd9U
As you can see here:
The code for both versions is available here
Live demo of the C# version.
The Java version was copy/pasted from your code and is using guava-18.0 and compiled with JDK8 x64 (I'm not a java expert so I'm just adding these in case it makes any difference).
I have a pre-written code which is used to cipher the given plain text or vice-versa .
The class has 3 methods, where in 2 methods can be used for encrypting and decrypting respectively.
public class SqlCipherUtil {
private Cipher ecipher;
private Cipher dcipher;
public String encryptString(String pStrPlainText) {
try {
generateKey();
byte[] utf8 = pStrPlainText.getBytes("UTF8");
byte[] enc = this.ecipher.doFinal(utf8);
return new BASE64Encoder().encode(enc);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public String decryptString(String pStrCipherText){
try {
generateKey();
byte[] dec = new BASE64Decoder().decodeBuffer(pStrCipherText);
byte[] utf8 = this.dcipher.doFinal(dec);
return new String(utf8, "UTF8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* This method is used to generate the encrypted key.
*/
private void generateKey() {
try {
byte[] decodedStr = new BASE64Decoder().decodeBuffer("rA/LUdBA/hA=");
SecretKey key = new SecretKeySpec(decodedStr, "DES");
this.ecipher = Cipher.getInstance("DES");
this.dcipher = Cipher.getInstance("DES");
this.ecipher.init(1, key);
this.dcipher.init(2, key);
} catch (Exception e) {
e.printStackTrace();
}
}
}
The key present in the class cannot be changed to any other key
in line byte[] decodedStr = new BASE64Decoder().decodeBuffer("rA/LUdBA/hA=");,
and it is giving an exception.
java.security.InvalidKeyException: Invalid key length: 9 bytes
at com.sun.crypto.provider.DESCipher.engineGetKeySize(DashoA13*..)
at javax.crypto.Cipher.b(DashoA13*..)
at javax.crypto.Cipher.a(DashoA13*..)
at javax.crypto.Cipher.a(DashoA13*..)
at javax.crypto.Cipher.a(DashoA13*..)
at javax.crypto.Cipher.init(DashoA13*..)
at javax.crypto.Cipher.init(DashoA13*..)
I tried out the below code, and I am getting exactly 8 bytes in the array.
public static void main(String[] args) throws IOException {
byte[] decodedStr = new BASE64Decoder().decodeBuffer("rA/LUdBA/hA=");
for(byte b : decodedStr){
System.out.print(b);
System.out.print(" ");
}
}
}
Any other combination of the key will make the byte array size more than 8 or less than 7.
What is the concept behind getting the byte array size 8 ?
What should be done to use custom key combination or our custom generated keys ?
Please answer Both the questions.
Any other combination of the key will make the byte array size more
than 8 or less than 7.
I doubt that. You're probably adding or removing the wrong characters; or at the wrong position. See: http://en.wikipedia.org/wiki/Base64
And yes 9 bytes is not a valid key length for DES. You could simply shorten it to the proper length. You do get 9 bytes because your base64 string is 3x4 characters long which will be decoded to 3x3 = 9 characters. Trim the output.
What is the concept behind getting the byte array size 8 ?
DES uses 56 Bit Keys. 8 Bytes = 64 Bit, so sufficient bits for the key.
What should be done to use custom key combination or our custom generated keys ?
Let the user enter a key that has at least 7 characters (56 Bit).
I really don't see why you use base64 in this sample at all - probably just because you copied it from somewhere? You just need a few random bytes. The common way to get those are to build a hash from any input the user gives and use bytes from that hash.
if your target is encode and decode the string, Use Base64.
public class PasswordCodecHandler {
Base64 codec = null;
public PasswordCodecHandler() {
codec = new Base64();
}
public String encode(String password) {
byte[] temp;
String encodedPassword = null;
temp = codec.encode(password.getBytes());
encodedPassword = new String(temp);
return encodedPassword;
}
public String decode(byte[] encodedPassword) {
byte[] temp;
String decodedPassword;
temp = codec.decode(encodedPassword);
decodedPassword = new String(temp);
return decodedPassword;
}
public static void main(String[] args) {
PasswordCodecHandler passwordCodecHandler = new PasswordCodecHandler();
String s1 = passwordCodecHandler.encode("password");
System.out.println(s1);
String s2 = passwordCodecHandler.encode("admin");
System.out.println(s2);
String s3 = passwordCodecHandler.encode("administrator");
System.out.println(s3);
String s4 = passwordCodecHandler.encode("123456");
System.out.println(s4);
}
}
For other data type : it can be java.lang.OutOfMemoryError based on the your memory allocation size
/* Download apache common-io.xxx. jar*/
public class CodecHandler {
Base64 codec = null;
public CodecHandler() {
codec = new Base64();
}
public byte[] encode(byte[] decoded) {
return codec.encode(decoded);
}
public byte[] decode(byte[] encoded) {
return codec.decode(encoded);
}
public static void main(String[] args) throws IOException {
File file = new File("D:/Test.mp4");
byte[] fileByteArray = FileUtils.readFileToByteArray(file);
CodecHandler codecHandler = new CodecHandler();
byte[] encoded = codecHandler.encode(fileByteArray);
System.out.println("Byte Size : " + encoded.length);
byte[] decode = codecHandler.decode(encoded);
FileUtils.writeByteArrayToFile(new File("C:/Test.mp4"), decode);
}
}
What is the concept behind getting the byte array size 8?
Your new, based-64–encoded key must be 12 characters long, ending with one, and only one, = character.
In base-64, the character = is a padding character, and it can only appear at the end of the encoded string. Base-64 encoding outputs a block of 4 characters from each input block of 3 bytes. If the input length is not a multiple of 3, the last block is padded.
In the encoded key, "rA/LUdBA/hA=", there are 12 characters, which can encode 9 bytes. But the last character is padding, which means the last byte should be ignored, leaving 8 bytes.
What should be done to use custom key combination or our custom generated keys?
First, you shouldn't use DES. It's too weak and insecure. But in general, the correct process for generating a secure key in Java is to use the KeyGenerator class. For (insecure) DES, you can generate a key and encode it with base-64 like this:
import java.util.Base64;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
…
KeyGenerator gen = KeyGenerator.getInstance("DES");
gen.init(56);
SecretKey secret = gen.generateKey();
String b64 = Base64.getEncoder().encodeToString(secret.getEncoded());
System.out.println(b64);
To use the key, decode it like this:
import java.util.Base64;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeySpec;
…
SecretKey key = new SecretKeySpec(Base64.getDecoder().decode(b64), "DES");
This is the PHP code I have.
function decrypt($s_input, $s_key, $s_iv) {
$s_decrypted = pack("H*" , $s_input); // Hex to binary
$s_decrypted = mcrypt_decrypt (MCRYPT_3DES, $s_key, $s_decrypted, MCRYPT_MODE_CBC, $s_iv); // 3des decryption
return $s_decrypted;
}
echo encrypt('c37551bb77f741d0bcdc16497b4f97b1','123456781234567812345678','12345678' );
what it basically does is to decrypt a 3des encrypted string (first it convert the hex string to binary using pack function and then does the actual decryption).
This perfectly works in PHP-4 and prints the "Hello World" message.
However, if I run the equivalent java code (jdk 1.6), it prints garbage output as - ¬ªmjV=7xl_ÓÄ^›*?.
Can someone help to troubleshoot this? Why Java is not properly decrypting the hex string.
private static String decrypt(String inputStr, String keyStr, String ivStr) throws Exception {
IvParameterSpec iv = new IvParameterSpec(ivStr.getBytes());
SecretKeySpec key = new SecretKeySpec(keyStr.getBytes(), "DESede");
inputStr = hexToString(inputStr, 2);
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decrypted = cipher.doFinal(inputStr.getBytes());
return new String(decrypted);
}
private static String hexToString(String input, int groupLength) {
StringBuilder sb = new StringBuilder(input.length() / groupLength);
for (int i = 0; i < input.length() - groupLength + 1; i += groupLength) {
String hex = input.substring(i, i + groupLength);
sb.append((char) Integer.parseInt(hex, 16));
}
return sb.toString();
}
public static void main(String[] args) throws Exception {
String decryptSignature = decrypt("c37551bb77f741d0bcdc16497b4f97b1", "123456781234567812345678", "12345678");
System.out.println(decryptSignature);
}
There are a few things you should check. You might find Encryption using AES-128 in Java to be of some assistance. There could be issues with differences between how you are handling keys in the PHP and Java code. Calling getBytes() on a String in Java without an encoding is almost always a bad idea. Plus the padding used could be a problem. From what I've seen PHP pads with null characters by default, which does not correspond to NoPadding in Java. Finally, the hexToString method should return a byte[] instead of a String. Add the result of calling Integer.parseInt(hex, 16) into an array:
byte[] results = new byte[input.length() / groupLength];
...
//inside the loop
results[i / groupLength] = (byte) Integer.parseInt(hex, 16);
...
return results;
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How do I use 3des encryption/decryption in Java?
How do I encrypt/decrypt a string of text using 3DES in java?
I found my answer. Duplicate question that didn't show up when I asked this one.
How do I use 3des encryption/decryption in Java?
From an old code:
public void testSymCypher(SecretKey k, String str)
throws BadPaddingException, IllegalBlockSizeException,
InvalidAlgorithmParameterException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchPaddingException
{
Cipher cip = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cip.init(Cipher.ENCRYPT_MODE,k);
byte[] ciphered = cip.doFinal(str.getBytes());
byte iv[] = cip.getIV();
// printing the ciphered string
printHexadecimal(ciphered);
IvParameterSpec dps = new IvParameterSpec(iv);
cip.init(Cipher.DECRYPT_MODE,k,dps);
byte[] deciphered = cip.doFinal(ciphered);
// printing the deciphered string
printHexadecimal(deciphered);
}
Notice than other usage of DESede are available in Java JDK 6:
DESede/CBC/NoPadding (168)
DESede/CBC/PKCS5Padding (168)
There is also ECB mode available (but be carreful to not use it twice !!), you don't need to use iv part in this case:
DESede/ECB/NoPadding (168)
DESede/ECB/PKCS5Padding (168)
To generate key for DESede:
KeyGenerator generatorDes = KeyGenerator.getInstance("DESede");
SecretKey skaes = generatorDes.generateKey();
Finally I recommand reading this document from SUN if you need to work on Java and Cryptography
We use this little helper class for password-based DES encryption from String to Hex String and back - not sure how to get this working with 3DES though:
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
public class DesHelper {
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DesHelper.class);
static final byte[] SALT = { (byte) 0x09, /* snip - randomly chosen but static salt*/ };
static final int ITERATIONS = 11;
private Cipher _ecipher;
private Cipher _dcipher;
public DesHelper(final String passphrase) {
try {
final PBEParameterSpec params = new PBEParameterSpec(SALT, ITERATIONS);
final KeySpec keySpec = new PBEKeySpec(passphrase.toCharArray());
final SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES")
.generateSecret(keySpec);
_ecipher = Cipher.getInstance(key.getAlgorithm());
_dcipher = Cipher.getInstance(key.getAlgorithm());
_ecipher.init(Cipher.ENCRYPT_MODE, key, params);
_dcipher.init(Cipher.DECRYPT_MODE, key, params);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
public String encrypt(final String string) {
try {
// Encode the string into bytes using utf-8
final byte[] bytes = string.getBytes("UTF-8");
// Encrypt
final byte[] enc = _ecipher.doFinal(bytes);
// Encode bytes to base64 to get a string
return bytesToHex(enc);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
public String decrypt(final String str) {
try {
// Decode base64 to get bytes
final byte[] dec = hexToBytes(str);
// Decrypt
final byte[] utf8 = _dcipher.doFinal(dec);
// Decode using utf-8
return new String(utf8, "UTF8");
} catch (final Exception e) {
log.info("decrypting string failed: " + str + " (" + e.getMessage() + ")");
return null;
}
}
private static String bytesToHex(final byte[] bytes) {
final StringBuilder buf = new StringBuilder(bytes.length * 2);
for (final byte b : bytes) {
final String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
buf.append("0");
}
buf.append(hex);
}
return buf.toString();
}
private static byte[] hexToBytes(final String hex) {
final byte[] bytes = new byte[hex.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
}
return bytes;
}
}
You would use this class like this:
public static void main(final String[] args) {
final DesHelper h = new DesHelper("blabla");
System.out.println(h.decrypt(h.encrypt("foobar")));
}
I wrote an article on this sometimes back. Please visit the following link in my blog that has a working, completed code with explanations and diagram.
View My Triple DES Encryption Article, Code Here
Hopefully you will find it helpful.
You may also consider using a stream cipher (e.g., OFB or CTR mode on top of a 3DES block encryption), so that you don't have to deal with padding the string to a multiple of the cipher blocksize.