Equivalent of the method RSACryptoServiceProvider signHash in Java - java

I'm trying to get the equivalent of the following C# method :
public byte[] SignHash(byte[] btHash, string SN)
{
string strSignature = string.Empty;
X509Store x509store = null;
x509store = new X509Store(StoreLocation.CurrentUser);
x509store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 x509 in x509store.Certificates)
{
if (x509.SerialNumber.ToLower().Contains(SN.ToLower()))
{
byte[] btSignature = null;
using (RSACryptoServiceProvider key = new RSACryptoServiceProvider())
{
key.FromXmlString(x509.PrivateKey.ToXmlString(true));
return key.SignHash(btHash, CryptoConfig.MapNameToOID("SHA256"));
}
break;
}
}
return null;
}
In Java language. Actually, I've come to this :
private static String SignHash(final byte[] btHash, String SerialNumber) throws Exception
{
KeyStore ks = null;
ks = KeyStore.getInstance("Windows-MY");
ks.load(null, null);
Boolean noValidCertificate = true;
Enumeration<String> en = ks.aliases();
ArrayList<String> lstAlias = Collections.list(en);
lstErreurs.add(lstAlias.size() + " certificate(s) found");
for (String aliasKey : lstAlias)
{
X509Certificate cert = (X509Certificate) ks.getCertificate(aliasKey);
Certificat = Base64Coder.encodeBase64String(cert.getEncoded());
Boolean blnCertificateFound = false;
if (SerialNumber != null && !SerialNumber.equals(""))
{
String SerialNumberCert = cert.getSerialNumber().toString(16);
if (SerialNumber.toLowerCase().contains(SerialNumberCert.toLowerCase())
|| SerialNumberCert.toLowerCase().contains(SerialNumber.toLowerCase()))
{
blnCertificateFound = true;
}
}
if (blnCertificateFound == false)
{
continue;
}
Provider p = ks.getProvider();
boolean isHashToSign = false;
for (String strToSign : input.split(";")) {
if(strToSign.length() == 44 && General.isBase64(strToSign)) {
isHashToSign = true;
break;
}
}
String algorithm = "";
if(isHashToSign)
{
algorithm = "RSA";
} else {
algorithm = "SHA256withRSA";
}
Signature sig = Signature.getInstance(algorithm, p);
PrivateKey key = (PrivateKey) ks.getKey(aliasKey, "1234".toCharArray());
if (key != null)
{
noValidCertificate = false;
sig.initSign(key);
String[] TabToSign = input.split(";");
String strResultSignature = "";
String separator = "";
for (String strToSign : TabToSign)
{
byte[] btToSign = null;
if(isHashToSign) {
btToSign = General.Base64_Decode_To_ByteArray(strToSign.getBytes(Charset.forName("UTF-8")));
} else {
btToSign = strToSign.getBytes(Charset.forName("UTF-8"));
}
sig.update(btToSign);
byte[] res = sig.sign();
String resB64 = Base64Coder.encodeBase64String(res);
strResultSignature += separator + resB64;
separator = ";";
}
return strResultSignature;
}
}
return null;
}
But getting the algorithm "RSA" does not work for signature. I finally sign the Hash of a Hash in Java. I would like to sign a SHA256 byte array hash without hashing it again. How can I come to this result ? (for information, I'm using the Windows Certificate Store, so I have to work with Sun MSCAPI provider).
EDIT 1 :
I tried with the algorithm "NONEwithRSA" but the signature result is different from the signature in .NET using SignHash method.
EDIT 2 :
The following thread : Difference between SHA256withRSA and SHA256 then RSA explains it is actually possible to sign a hash, but that method requires BouncyCastle.
I can not operate with BouncyCastle because I need to use sun MSCAPI provider (Windows Certificate Store). I have to find an alternative to BouncyCastle (unless BC allows us to use the Sun MSCAPI provider).

(The answer was completely rewritten. Some less interesting thoughts and snippets can be found in the previous revisions)
A call to SignHash(btHash, CryptoConfig.MapNameToOID("SHA256")) does a PKCS#1 v1.5 signature (RSASSA-PKCS1-v1_5), e.g.:
byte[] btHash = new byte[] { 0x57, 0x91, 0x16, 0xB6, 0x3E, 0x06, 0x58, 0x83, 0x24, 0x8C, 0x07, 0x16, 0xDA, 0x6A, 0x03, 0x4D, 0x23, 0x37, 0x0B, 0x32, 0x1C, 0xA0, 0x80, 0x08, 0x1F, 0x42, 0x03, 0x81, 0x8E, 0x54, 0x3A, 0xC6 };
X509Certificate2 cert = new X509Certificate2("dummy.p12", "1234", X509KeyStorageFlags.Exportable);
using (RSACryptoServiceProvider key = new RSACryptoServiceProvider())
{
key.FromXmlString(cert.PrivateKey.ToXmlString(true));
byte[] ret = key.SignHash(btHash, CryptoConfig.MapNameToOID("SHA256"));
}
Gives a signature:
0A5003E549C4E4310F720076A5A4D785B165C4FE352110F6CA9877EB9F364D0C40B0197110304D6F92E8BD40DFD38DB91F356601CDD2CD34129BC54492C2D7F371D431150288A95C21E47533F01A9FA4977439FF9594C703380BEDF49A47A7B060ECAC26AEB53B8732D93E18FAD3B2D5889B3311C1B0D4F9F6B318169BDEB143D771DEFB56BAFE49B2B59F172757D4273EF369AFCB32490EC954E17599BD66D4E3BDB345B860748DB0C3B5A272ECFA546E65F2D4C87870CC62D91680AB71DB52DE618C006356258A941E8F36A5DCC7A06BA6DACAC3DC35F482B168107B4D7DA6C19A56FEDC247232DD7210CA9DB7273AA9AE6A90A8A4DFEB64BA0FBC830AB922
Which contains a PKCS#1 v1.5 padded DigestInfo and hash (when decrypted using the public key):
0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003031300D060960864801650304020105000420579116B63E065883248C0716DA6A034D23370B321CA080081F4203818E543AC6
As you have only the hash (and not the data) to be signed you need to use the NONEwithRSA algorithm in java (which should perform a PKCS#1 v1.5 padded signature of the input data without any hashing) and generate the correct input DigestInfo with the hash OID manually. Like that (with the help of Apache Commons Lang)::
byte[] btHash = new byte[] { ....the same.... };
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream("dummy.p12"), "1234".toCharArray());
PrivateKey privKey = (PrivateKey)keystore.getKey("Dummy", "1234".toCharArray());
byte[] asn=ArrayUtils.addAll(new byte[] { (byte)0x30, (byte)0x31, (byte)0x30, (byte)0x0d, (byte)0x06, (byte)0x09, (byte)0x60, (byte)0x86, (byte)0x48, (byte)0x01, (byte)0x65, (byte)0x03, (byte)0x04, (byte)0x02, (byte)0x01, (byte)0x05, (byte)0x00, (byte)0x04, (byte)0x20}, btHash);
Signature signature = Signature.getInstance("NONEwithRSA");
signature.initSign(privKey);
signature.update(asn);
byte[] ret = signature.sign();
Which gives the same signature as the C# code (using the default SunJCE/SunRsaSign providers).
The SunMSCAPI provider supports the NONEWithRSA algorithm with a restriction. Citing sun.security.mscapi.RSASignature javadoc:
NOTE: NONEwithRSA must be supplied with a pre-computed message digest.
Only the following digest algorithms are supported: MD5, SHA-1,
SHA-256, SHA-384, SHA-512 and a special-purpose digest algorithm which
is a concatenation of SHA-1 and MD5 digests.
Which at first sight might work for this scenario. Unfortunately:
Signature mscapiSignatureNoneWithRSA = Signature.getInstance("NONEwithRSA", "SunMSCAPI");
mscapiSignatureNoneWithRSA.initSign(mscapiPrivKey);
mscapiSignatureNoneWithRSA.update(btHash);
byte[] mscapiSignatureNoneWithRSA_btHash = mscapiSignatureNoneWithRSA.sign();
Gives a different signature:
CE26A9F84A85037856D8F910141CE7F68D6CAAB416E5C2D48ACD9677BBACCB46B41500452A79018A22AB1CA866DD878A76B040A343C1BABCDB683AFA8CE1A6CCCA48E3120521E8A7E4F8B62B453565E6A6DC08273084C0748C337724A84929630DC79E2EB1F45F5EEBA2148EC0CA5178F2A232A2AE8A5D22BB06C508659051CD1F5A36951B60F6322C5AEB5D4461FABE4D84E28766501A1583EC2A5D8553C163EC8DB9E80EF972233516BEC50AAC38E54C8B5E3B3DEAE90F37A1D230CD976ABEEE97A4601461E842F0F548DA7A74645AAB42586044B084E6A9F3EFE409EE12D98EB0B16CDC2C0AB75BF5DC9B52EBB7E3303C53A2B1BDF14000B99112B1871A6A
Which contains only a PKCS#1 v1.5 padded value of the hash (there is no ASN.1 DigestInfo sequence /which is wrong in this case/):
0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00579116B63E065883248C0716DA6A034D23370B321CA080081F4203818E543AC6
Trying to sign the DigestInfo from the SunJCE example gives an exception:
java.security.SignatureException: Message digest is too long
(Here and here are the reasons for this behavior.)
An alternative way to generate the signature using RSA private key encryption which gives the same signature as the C# code with SunJCE provider (using the same asn variable as above):
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, privKey);
byte[] ret = cipher.doFinal(asn);
Does not work with the SunMSCAPI provider:
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "SunMSCAPI");
cipher.init(Cipher.ENCRYPT_MODE, mscapiPrivKey);
byte[] ret = cipher.doFinal(asn1);
As it gives:
4A540DFAD44EBDAE89BF5DD52121DA831E7C394E0586DC9EAEF949C466E649979E19DF81A44801EBD80B8946605B4EFCED53011A844A3A4E023136F0CDEAA57EAAB1EA1FA75400B3B2D5FAB3955BEB13A178AC03DED6AACA0571412B74BCE30E772082A368B58E94D8E20D8F2A116BA5B3881824A014281E9F04BD687C087ACF7164CAF7C74274BA356A671ADA2BB4142504DB2883AFEDA563C6E590BC962725D6697402AB24391409F50D7D16B8BF64A1C0224C379EF9C7B8E493BE889A70674C3AEEC524366DBF9DE36FEE01F186FC00DE2F06096C46CC873D37E194CB217FBFCCF450C1F96C804022E25E1589DF67247927AAD59C66294B027DD5EE991D46
Which decrypted using the public key gives a nonsense:
3A9E0F985D1CF2CFDB45F201A68EF0F241ADBAED2D945FD36451CB4BE77D9B30D977004F95E6EDC208805E62870CD19D87C5E7F4E4C1AC2546F7F9C410299C9D203C47C2B547BAA55DA05C44DACB7E07C3F0DB99AE291E48A67EE089F8DA34EB1AABE352A7F94B082CFB167C0FE90761B79FCE238A0F3D0D917CA51220EEA9A43877703FC06CDC1F13A77CA9904E3660F7AD84DE0C34C877B737C20B1A117E60D69E6766054A30B95E6E88CF2C11AEE5CE30F2DD780C0334BE085302E73E0E5BB096893B7155E7A48CA16DD5EA9FC6F63090F7223E7FBAAA133BDFDA7251F412E395F4D8A462569BC5DCC34C2DF7D996BB3278C3F6B0E1EE9E729925937C8BAE
But (more interestingly) contains a valid PKCS#1 v1.5 padded encrypted plaintext when decrypted with the private key:
000211F7946FAD6BDB18830F8DD1238FD7EFCCFF041D55B88FBABDAAA6B06A5E9FD7556EB33678D9954D26E07B2FCE6D7304386DBDFC352C9932E2BA1794A3A0E0F6D78AA656DEB36CC483171A77ABF34408F4BF60661ECA79852B8E39C1A710976208FFBF6BE0DFB566149E6C5838762316F394B70BDF6D494F8C43C42CB6E527292DEF9204712CB24AC82C572BBC0E70A298D5FB050A27B54AFFA1332EEF37A14E65D379968BCE717BEC37C67A180DE943AAF2FE83560D33BC588E11B85D1C3391CCB13E4A80F57166BAC9003031300D060960864801650304020105000420579116B63E065883248C0716DA6A034D23370B321CA080081F4203818E543AC6
Which means that although given a private key for the encryption operation the SunMSCAPI uses the public key part (I did not dig into the implementation details to find the reason for this behavior).
So (AFAIK) the SunMSCAPI provider can not be directly used in your scenario...
(Please note that you will get a different result for each encryption run as the PKCS#1 v1.5 encryption padding contains a random data)
Fortunately there are some alternative choices:
[A] Abuse the SunMSCAPI internal API to perform the signature like that (again with the help of Apache Commons Lang):
// Obtain the handles
long hCryptKey = (Long)MethodUtils.invokeMethod(mscapiPrivKey, "getHCryptKey");
long hCryptProvider = (Long)MethodUtils.invokeMethod(mscapiPrivKey, "getHCryptProvider");
// Call the internal native method
Class<?> internalClass = Class.forName("sun.security.mscapi.RSASignature");
Method internalSignHashMethod = internalClass.getDeclaredMethod("signHash", boolean.class, byte[].class, int.class, String.class, long.class, long.class);
internalSignHashMethod.setAccessible(true);
byte[] res = (byte[])internalSignHashMethod.invoke(internalClass, false, btHash, btHash.length, "SHA-256", hCryptProvider, hCryptKey);
ArrayUtils.reverse(res); // Make it big endian
Which gives the same result as the C# code.
But is strongly dependand on the underlying SunMSCAPI implementation which can change at any moment
[B] Use JNI/JNA and call the winapi functions directly
Which is a cleaner approach as it depends on a public API
I have found this project, but have not given it a try
Good luck!
Appendix: RSA Private key used in the examples:
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDoZFvkEpdzXwSw
9g6cDxg9n/khCjLIO7E8VQFzu80C0iR0C6K05SHvTFEdssmzZmdCQi092ReSJRPH
yAOQUnlcMuCpi0m62ufZ4yNkZX5sH3fjHkP1FMv5CPtJOIArGFCMS4CufXu2XkXh
dbJuCLPJsUuiRsaoRg0Q6a8QVqWAR1oyVojTNFqzZWTLD46lQQIvINOrIeYvKklU
FUNcmq8PyArwEvxaDeiop4gVyizx7n7v213FjAXMfEG920O4DlnKjObdi1+PhejT
1RUxRUipTmAI2d3JmACpYH6+Il8Ck61wmKQ9IjoTstNeRfKGEkxH9RKP2P4ko5w9
8YfToVDXAgMBAAECggEAI5vNIMNghYMXuu3ZCzyc4ER07gUcBuZun+n+kPdD0JzW
jRmDUuiRLJOrEjvlACI+zD5LpGBxZilcQI57TU/13JTHK/N11rXYNODC+Y07s+GW
gyyOCS2om34u0udfbDsLjJO9If+ER0tmtcdNEeMveUY7aqAhrIMfWWoVMxGzxlXd
0kHWl4blnisjc01xCG4WcXVItyA6F8WZ3kL+BTeR5/3IwM72r9k7fcBkJZPLJZff
oZ2W+whGa9UXAkt6DQ6PlWMvt+AVcu84f8k/4FRRUNbY1OslO7zHbEc1K5qibpjb
6Tlfg2G+bux/1oCJ59bdyRP7FQMmgjLx49H17mwcgQKBgQD1j4HRtwSPnS+INPS4
nVpfEtb+wXGeDLCMAgdesgXYfr5JWJKJCKW65/j2TkJ/xoN8L1z8TeE6o6Q3ZHJ9
QtcM1ifCpNCnjjalbkm9BG4bCPy+5MUIuS5oRtJjwb7mPTxzpq4DIj3G2ooY5F2i
9Nyqde3bEvWn910yqQgI6CjOtwKBgQDyRYkU46gKLhp98gJ0zdm3KNZ/TBE5zsO6
rDnwFPLGxanVNMzkIQX/LXAQOaNK1WD8fmKG+iixDVLyJYtVtuvTQLOHkOOTXw44
QY4BGh+wbS0HrfKd7Qcpt/3HTCKq9eW33+jggyBc+fa+LDIGpdbO16SBCP3Cb7k6
9gtBN5du4QKBgQCKriVO3uGAifESJ3Yd3R/wmZ85+N3FuLzsFSk8XaXXgpzMp2z6
XxvZ1rBPyhrcNqyDMex9wS32A/z2G5BdFaaF5VxHHPWJ61MJUqPqT9ovAoBa/rAY
IR0IXxbqp7y8ItFFL1kPBAOHjlx3emE3arpEup0+IBMEbTsBJV0YSqThOQKBgFRf
syX7QwKIm+FQ71oOdsw7BLjAnR8syy2v3V2nbgWbwVHnWZP5jEUaZfTAngXp2iUV
PusTJCjFIyYBvUzUr7yaw+tqolcou6ML8ZCgsHiZDR2njt9BNUVqNo+6DDjN+nrX
GBtYj2TSCQSiD6oRB4Zxw3DM2NNmZXQLTFAiNDMBAoGBAJOu4+nVB8iUE3JBsrM5
WkwBivqTyo9pusxwEs+GHnkVFsFubFKHda04220JMRVevOuf48DPgvlW6UCTojyr
85dFon9tV0qyi9Ehc0OdXqOjmx0y+wdR6ZqG+x+e6JGiYeReIa4XBrq0cLHlzrNY
8UwL0QLJpuaQZEbqhyOGMNKE
-----END PRIVATE KEY-----

Related

Why the same result both DES Encrypt and DESede Encrypt?

I'm try to be compatible Encrypt/Decrypt both C# and Java.
As I know the default mode is 'ecb/pkcs5' in Java, and 'cbc/pkcs7' in C#.
So I match these things.
1st question is that PKCS7 and PKCS5 are compatible each other??,
there is no PKCS7 in Java so I use PKCS5. but I can get same encrypted data [even the padding-way is different ,pkcs7/pkcs5,] Is it possible? or these are compatible?
2nd question is that Why I get same result even though the mode, way are all different?
I compare 'DES-ECB / DES-CBC / TripleDES-ECB' these things. and C# is working well, results are all different.
Input > HELLO Output > (ECB)/dZf3gUY150= (CBC) V17s5QLzynM= (Triple)sWGS0GMe1jE
but I get same reulst in Java ..
Input > HELLO Output > (ECB)/dZf3gUY150= (CBC)/dZf3gUY150= (Triple)/dZf3gUY150=
When debugging the flow is right.
Here is my code.
C#
public static string Encrypt_DES(string originalString, byte[] key, string mode)
{
DESCryptoServiceProvider cryptoProvider = new DESCryptoServiceProvider();
if (mode.Equals("ECB"))
cryptoProvider.Mode = CipherMode.ECB;
else if (mode.Equals("CBC"))
{
cryptoProvider.Mode = CipherMode.CBC;
cryptoProvider.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
}
cryptoProvider.Padding = PaddingMode.PKCS7;
MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoProvider.CreateEncryptor(key, key), CryptoStreamMode.Write);
StreamWriter writer = new StreamWriter(cryptoStream);
writer.Write(originalString);
writer.Flush();
cryptoStream.FlushFinalBlock();
writer.Flush();
return Convert.ToBase64String(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
}
public static string Encrypt_TripleDES(string source, string key)
{
TripleDESCryptoServiceProvider desCryptoProvider = new TripleDESCryptoServiceProvider();
MD5CryptoServiceProvider hashMD5Provider = new MD5CryptoServiceProvider();
byte[] byteHash;
byte[] byteBuff;
byteHash = hashMD5Provider.ComputeHash(Encoding.UTF8.GetBytes(key));
desCryptoProvider.Key = byteHash;
desCryptoProvider.Mode = CipherMode.ECB; //CBC, CFB
desCryptoProvider.Padding = PaddingMode.PKCS7;
byteBuff = Encoding.UTF8.GetBytes(source);
string encoded = Convert.ToBase64String(desCryptoProvider.CreateEncryptor().TransformFinalBlock(byteBuff, 0, byteBuff.Length));
return encoded;
}
Java(Android)
public String Encrypt(String str, String desKey, String mode) {
try {
KeySpec keySpec = null;
SecretKey key = null;
Cipher ecipher = null;
if (desKey.length() == 8) {
keySpec = new DESKeySpec(desKey.getBytes("UTF8"));
key = SecretKeyFactory.getInstance("DES").generateSecret(keySpec);
if(mode.equals(ECB)){
ecipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
ecipher.init(Cipher.ENCRYPT_MODE, key);
}else if (mode.equals(CBC)){
ecipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivBytes);
ecipher.init(Cipher.ENCRYPT_MODE, key,ivSpec);
}
} else if (desKey.length() == 24) {
keySpec = new DESedeKeySpec(desKey.getBytes("UTF8"));
key = SecretKeyFactory.getInstance("DESede").generateSecret(keySpec);
ecipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
ecipher.init(Cipher.ENCRYPT_MODE, key);
}
byte[] data = str.getBytes("UTF-8");
byte[] crypt = ecipher.doFinal(data);
return Base64.encodeToString(crypt, 0);
} catch (Exception ex) {
Log.d("ZVM", ex.getMessage());
}
return null;
}
As I understand 'IV' is for CBC, When making password, it is mixed with IV(not the key but like key). Is it right?
Thanks.
PKCS7 and PKCS5 are compatible each other
PKCS#5 and PKCS#7 paddings are compatible (equal) for DES. For AES, Java actually uses PKCS#7 padding even though you would write AES/xyz/PKCS5Padding.
Why I get same result even though the mode, way are all different?
First, let's see how Java behaves. The ciphertexts for DES-ECB, DES-CBC and DESede-ECB are all equal. This is correct if
the key is the same (DES supports only 8 byte keys, but Triple DES supports 8, 16 and 24 byte keys where non-24 byte keys are expanded to 24 byte keys),
the plaintext is the same,
the plaintext is less than 8 bytes long (block size of DES/Triple DES) and
the IV is an all 0x00 bytes IV.
Those are all true in the Java code. If you have trouble grasping that, combine the encryption routines for the ECB and CBC modes of operation.
The result of Triple DES might be a bit confusing. I assume that you've taken your 8 byte key for DES and replicated it either twice or thrice for use in Triple DES. This is an issue, because Triple DES encryption consists of three steps of normal DES: EDE means Encryption + Decryption + Encryption. If all the three subkeys are the same, the one of the Encryption steps cancels out with the Decryption step and the whole thing is equivalent to a single DES encryption.
Let's see why C# behaves differently:
The ciphertext from DES-CBC is different from DES-ECB, because the IV is not an all 0x00 bytes IV. cryptoProvider.CreateEncryptor(key, key) creates an Encryptor with the IV set to key (the second argument). That's not what you want. Just use cryptoProvider.CreateEncryptor() instead.
The ciphertext from DESede-ECB is different from DES-ECB, because you're running the key through a hash function. The key is therefore different.
Don't use DES nowadays. It only provides 56 bit of security. AES would be a much better, because it's more secure with the lowest key size of 128 bit. There is also a practical limit on the maximum ciphertext size with DES. See Security comparison of 3DES and AES.

3DES (DESede)- Decrypt encrypted text (done by JAVA) in C#

The encrypted text is done in JAVA (which we have no JAVA background at all)
The decryption will be in C#, and here is the code
public static string DecryptString(string Message, string Passphrase)
{
byte[] Results;
UTF8Encoding UTF8 = new UTF8Encoding();
MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();
byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(Passphrase));
// byte[] TDESKey = UTF8.GetBytes(Passphrase);
TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();
TDESAlgorithm.Key = TDESKey;
// TDESAlgorithm.Mode = CipherMode.CTS;
TDESAlgorithm.Padding = PaddingMode.Zeros;
byte[] DataToDecrypt = Convert.FromBase64String(Message);
try
{
ICryptoTransform Decryptor = TDESAlgorithm.CreateDecryptor();
Results = Decryptor.TransformFinalBlock(DataToDecrypt, 0, DataToDecrypt.Length);
}
finally
{
TDESAlgorithm.Clear();
HashProvider.Clear();
}
return Encoding.UTF8.GetString(Results);
}
Encrypted Java code is
public String encryptData(String privateKey, String rawData)
{
Cipher cipher = null;
try
{
cipher = Cipher.getInstance(DESEDE_ENCRYPTION_SCHEME);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(privateKey));
byte[] plainText = rawData.getBytes(UNICODE_FORMAT);
byte[] encryptedText = cipher.doFinal(plainText);
return new String(Base64.encodeBase64(encryptedText));
}
}
However, when tried to decrypt, got the error message: BAD DATA
Where am I missing here?
You are not using MD5 in Java, so you should not be using it in your .NET for computing the hash.
Your key should have been generated using a specific encoding and same you should use in .NET.
Please note, there is some fundamental difference in java KeySpec and the Key being used for TripleDESCryptoServiceProvider. As mentioned by Microsfot https://msdn.microsoft.com/en-us/library/system.security.cryptography.tripledescryptoserviceprovider.aspx
Triple DES only supports "key lengths from 128 bits to 192 bits in increments of 64 bits"
So you need to convert your key appropriately before assigning. To do this you can use the Array.Resize method as following.
byte[] TDESKey = Encoding.UTF8.GetBytes(Passphrase);
System.Array.Resize(ref TDESKey , 192 / 8);
Hope this will help.

c# RSA encrypt with private key

Encryption and Decryption successful when encrypt with public key and decrypt with private key :
C# encryption with public key(Successful)
public string EncryptData(string data) {
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(xml); //public key
var cipher = rsa.Encrypt(Encoding.UTF8.GetBytes(data), false);
return Convert.ToBase64String(cipher );
}
Java decryption with private key(Successful)
public static void decrypt() throws Exception{
byte[] modulusBytes = Base64.getDecoder().decode(mod);
byte[] dByte = Base64.getDecoder().decode(d);
BigInteger modulus = new BigInteger(1, (modulusBytes));
BigInteger exponent = new BigInteger(1, (dByte));
RSAPrivateKeySpec rsaPrivKey = new RSAPrivateKeySpec(modulus, exponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PrivateKey privKey = fact.generatePrivate(rsaPrivKey);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privKey);
byte[] cipherData = Base64.getDecoder().decode(cipherByte);
byte[] plainBytes = cipher.doFinal(cipherData);
System.out.println(new String(plainBytes));
}
Problem is Here
When c# encrypt with private key and java decrypt with public key bad padding error occur:
C# encryption with private key(Fail)
public stringEncryptData(string data) {
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(xml); //private key
var cypher = rsa.Encrypt(Encoding.UTF8.GetBytes(data), false);
return Convert.ToBase64String(cypher);
}
java decryption with public key (Fail)
public static void decryptPublic() throws Exception{
byte[] modulusBytes = Base64.getDecoder().decode(mod);
byte[] expBytes = Base64.getDecoder().decode(exp);
BigInteger modulus = new BigInteger(1, (modulusBytes));
BigInteger exponent = new BigInteger(1, (expBytes));
RSAPublicKeySpec pubKey = new RSAPublicKeySpec(modulus, exponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey publicKey = fact.generatePublic(pubKey);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, publicKey );
byte[] cipherData = Base64.getDecoder().decode(cipherByte);
byte[] plainBytes = cipher.doFinal(cipherData);
System.out.println(new String(plainBytes));
}
I understand public key should use to do encryption and private key for decryption.But in my situation, i need to sent out public key to mutiple clients for decryption on a text encrypted by its private key. Text should be non readable by others except client.
Can anyone see what problem on my code, or suggest a better solution to my problem.
RSA encryption is only secure if a (secure) padding scheme is being used. RSA encryption schemes have been specified in PKCS#1 standards by RSA laboratories (now part of EMC2). These have been copied into RFC, such as RFC 3447: Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.1.
For the purposes of this document, an encryption scheme consists of
an encryption operation and a decryption operation, where the
encryption operation produces a ciphertext from a message with a
recipient's RSA public key, and the decryption operation recovers the
message from the ciphertext with the recipient's corresponding RSA
private key.
So encryption with a private key is an undefined operation.
So what to do now:
securely distribute private keys instead of public keys
generate key pairs and securely transport the public key to the sender
if you require authentication/integrity instead of confidentiality, use signature generation instead of encryption
And, whatever you do, read into Public Key Infrastructure (PKI). It's a far stretching subject that you need to understand before you can apply it.
Encrypting with the private key/decrypting with the public key is a legitimate operation in RSA, however it is not used to protect data, it is instead used to authenticate the source of the data and its integrity. In this context the encryption operation is more usually called "signing".
Encrypting using the private key to protect data as you describe is insecure and so the fact that it is not easily done is likely intentional and intended to prevent incorrect use of the algorithm.
Distributing your private key to clients as suggested in the comments is also unwise since you have no control over who they may pass the key onto (accidentally or otherwise).
If you wish to encrypt data so that it can be decrypted by multiple distinct parties, then you should have each of them provide you with their own public key, and use that to encrypt the data separately for each client.

Send RSA public key from Java server to C# client [duplicate]

I have public key generated using java with algorithm RSA and able to reconstruct using following code:
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(arrBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
publicKey = keyFactory.generatePublic(pubKeySpec);
QUESTION
How to construct PublicKey on dotnet side using csharp?
sample public key would be:, In above code i pass data contained in element encoded
<sun.security.rsa.RSAPublicKeyImpl resolves-to="java.security.KeyRep">
<type>PUBLIC</type>
<algorithm>RSA</algorithm>
<format>X.509</format>
<encoded>MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMf54mcK3EYJn9tT9BhRoTX+8AkqojIyeSfog9ncYEye
0VXyBULGg2lAQsDRt8lZsvPioORZW7eB6IKawshoWUsCAwEAAQ==</encoded>
</sun.security.rsa.RSAPublicKeyImpl>
Unfortunately, C# doesn't provide any simple way to do this. But this will correctly decode an x509 public key (make sure to Base64 decode the x509key parameter first):
public static RSACryptoServiceProvider DecodeX509PublicKey(byte[] x509key)
{
byte[] SeqOID = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
MemoryStream ms = new MemoryStream(x509key);
BinaryReader reader = new BinaryReader(ms);
if (reader.ReadByte() == 0x30)
ReadASNLength(reader); //skip the size
else
return null;
int identifierSize = 0; //total length of Object Identifier section
if (reader.ReadByte() == 0x30)
identifierSize = ReadASNLength(reader);
else
return null;
if (reader.ReadByte() == 0x06) //is the next element an object identifier?
{
int oidLength = ReadASNLength(reader);
byte[] oidBytes = new byte[oidLength];
reader.Read(oidBytes, 0, oidBytes.Length);
if (oidBytes.SequenceEqual(SeqOID) == false) //is the object identifier rsaEncryption PKCS#1?
return null;
int remainingBytes = identifierSize - 2 - oidBytes.Length;
reader.ReadBytes(remainingBytes);
}
if (reader.ReadByte() == 0x03) //is the next element a bit string?
{
ReadASNLength(reader); //skip the size
reader.ReadByte(); //skip unused bits indicator
if (reader.ReadByte() == 0x30)
{
ReadASNLength(reader); //skip the size
if (reader.ReadByte() == 0x02) //is it an integer?
{
int modulusSize = ReadASNLength(reader);
byte[] modulus = new byte[modulusSize];
reader.Read(modulus, 0, modulus.Length);
if (modulus[0] == 0x00) //strip off the first byte if it's 0
{
byte[] tempModulus = new byte[modulus.Length - 1];
Array.Copy(modulus, 1, tempModulus, 0, modulus.Length - 1);
modulus = tempModulus;
}
if (reader.ReadByte() == 0x02) //is it an integer?
{
int exponentSize = ReadASNLength(reader);
byte[] exponent = new byte[exponentSize];
reader.Read(exponent, 0, exponent.Length);
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAKeyInfo = new RSAParameters();
RSAKeyInfo.Modulus = modulus;
RSAKeyInfo.Exponent = exponent;
RSA.ImportParameters(RSAKeyInfo);
return RSA;
}
}
}
}
return null;
}
public static int ReadASNLength(BinaryReader reader)
{
//Note: this method only reads lengths up to 4 bytes long as
//this is satisfactory for the majority of situations.
int length = reader.ReadByte();
if ((length & 0x00000080) == 0x00000080) //is the length greater than 1 byte
{
int count = length & 0x0000000f;
byte[] lengthBytes = new byte[4];
reader.Read(lengthBytes, 4 - count, count);
Array.Reverse(lengthBytes); //
length = BitConverter.ToInt32(lengthBytes, 0);
}
return length;
}
The above code is based off of this question (which only worked for certain key sizes). The above code will work for pretty much any RSA key size though, and has been tested with the key you provided as well as 2048-bit and 4096-bit keys.
An alternative solution would be to generate a certificate using a tool (XCA is a good one), export the cert to a p12 (PKCS12) file and then load the cert in both Java and C# to get at the keys.
In C# you can load a PKCS12 file using the X509Certificate2 class.
X509Certificate2 cert = new X509Certificate2(certificateFile, certificatePassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
RSACryptoServiceProvider provider1 = (RSACryptoServiceProvider)cert.PublicKey.Key;
RSACryptoServiceProvider provider2 = (RSACryptoServiceProvider)cert.PrivateKey;
In Java you can load a PKCS12 file using the KeyStore class.
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream(certificateFile), certificatePassword.toCharArray());
Key key = keystore.getKey(certName, certificatePassword.toCharArray());
Certificate cert = keystore.getCertificate(certName);
PublicKey publicKey = cert.getPublicKey();
KeyPair keys = new KeyPair(publicKey, (PrivateKey) key);
In Java, cast publicKey from PublicKey to RSAPublicKey.
That has getModulus and getExponent will get you BigIntegers, from which you use toByteArray to get the bytes.
I don't know Java keeps leading 0s in the BigInteger class, so check if you have to strip leading null (0x00) bytes from the public exponent.
Encode the byte arrays into base 64 using either the Apache Commons Codec or Java 8's Base64 Encoder.
You may need to check byte order (maybe reverse the modulus, not sure).
Serialize these by constructing this XML: "<RSAKeyValue><Modulus>{your base 64 encoded public modulus here}</Modulus><Exponent>{your base 64 encoded public exponent here}</Exponent></RSAKeyValue>".
In CSharp:
var rsaCsp = new RSACryptoServiceProvider(o.BitLength);
rsaCsp.FromXmlString(xmlRsaKeyValue);
Now you have an RSA CSP loaded with your public key.
The same process can be extended to load a private key by adding P, Q, DP, DQ and InverseQ XML elements.
Hello you can try this way also,
private static string[] GenerateXMLPrivatePublicKeys(){
string[] keys = new string[2];
RSA rsa = new RSACryptoServiceProvider(2048);
string publicKey = rsa.ToXmlString(false);
string privateKey = rsa.ToXmlString(true);
keys[0] = publicKey;
keys[1] = privateKey;
return keys;
}

RSA ENCRYPTION IN ANDROID AND DECRYPTION IN SERVER SIDE

I have made a https api request from android to server. The API request contains one parameter that needs to be encrypted before it is send (i.e. it's a password). RSA/ECB/PKCS1Padding is the encryption used in both end.
Encryption in android side does the following things:
/*Encrypt the password using public key.public key is obtained from generateRsaPublicKey(BigInteger modulus, BigInteger publicExponent) function)*/
public static String rsaEncrypt(String originalString, PublicKey key) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherByte = cipher.doFinal(original);
return bytesToHex(cipherByte);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//generate public key with given module and exponent value
public static PublicKey generateRsaPublicKey(BigInteger modulus, BigInteger publicExponent) {
PublicKey key = null;
try {
key = KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, publicExponent));
return key;
} catch (Exception e) {
Log.e("error", e.toString());
// return null;
}
return key;
}
// Helper methods
final protected static char[] hexArray = "0123456789abcdef".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
// Log.d("byte array representaion","value in integrer"+v);
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
below is the source for decrypting the password on the server
// *** setup private key
RSAPrivateKeySpec privateRPKS
= new RSAPrivateKeySpec(new BigInteger(gModulusPlainS, 16), new BigInteger(privateExponentPlainS, 16));
KeyFactory keyFactoryKF = KeyFactory.getInstance("RSA");
RSAPrivateKey gPrivateKeyRPK = (RSAPrivateKey) keyFactoryKF.generatePrivate(privateRPKS);
// *** setup cipher
Cipher cipherC = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipherC.init(Cipher.DECRYPT_MODE, gPrivateKeyRPK);
// *** decrypt hex-encoded cipherTxS
byte[] baCipherText = hexToBin(cipherTxS.getBytes());
byte[] baPlainText2 = cipherC.doFinal(baCipherText);
String decryptedTextS = new String(baPlainText2);
But I got the following error log
javax.crypto.IllegalBlockSizeException: Data size too large
at com.ibm.crypto.provider.RSASSL.a(Unknown Source)
at com.ibm.crypto.provider.RSASSL.engineDoFinal(Unknown Source)
at javax.crypto.Cipher.doFinal(Unknown Source)
javax.crypto.BadPaddingException: Not PKCS#1 block type 2 or Zero padding
But it is working on websight part. Why it isn't working in android?
Thank you for your kindness to looking my code.
You are sending the ciphertext as string, if cipherTxS.getBytes() is indeed a string to byte array conversion. Ciphertext should be either kept in binary or possibly encoded using base 64 encoding.
Thanks to Alex Klyubin, Android Security Engineer. I have got answer from Here
Developers who use JCA for key generation, signing or random number generation should update their applications to explicitly initialize the PRNG with entropy from /dev/urandom or /dev/random.Also, developers should evaluate whether to regenerate cryptographic keys or other random values previously generated using JCA APIs such as SecureRandom, KeyGenerator, KeyPairGenerator, KeyAgreement, and Signature.
Code comment: Install a Linux PRNG-based SecureRandom implementation as the default, if not yet installed.This is missing from jellybean onwards.
Sample code implementation

Categories

Resources