I am successfully running RSA encryption/decryption in Java. This is how I generated the key.
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair keypair = kpg.generateKeyPair();
oos.writeObject(keypair);
But now I need to integrate my system with .Net code. Is it possible to export this KeyPair object into XML in the following format(as that .Net code can only accept keys in XML format):
<RSAKeyValue>
<Modulus>.....</Modulus>
<Exponent>......</Exponent>
<P>.....</P>
<Q>....</Q>
<DP>.......</DP>
<DQ>......</DQ>
<InverseQ>.........</InverseQ>
<D>........</D>
</RSAKeyValue>
Try this:
// key pair is in 'kp'
KeyFactory kf = KeyFactory.getInstance("RSA");
RSAPrivateCrtKeySpec ks = kf.getKeySpec(
kp.getPrivate(), RSAPrivateCrtKeySpec.class);
System.out.println("<RSAKeyValue>");
System.out.println(" <Modulus>" + ks.getModulus() + "</Modulus>");
System.out.println(" <Exponent>" + ks.getPublicExponent() + "</Exponent>");
System.out.println(" <P>" + ks.getPrimeP() + "</P>");
System.out.println(" <Q>" + ks.getPrimeQ() + "</Q>");
System.out.println(" <DP>" + ks.getPrimeExponentP() + "</DP>");
System.out.println(" <DQ>" + ks.getPrimeExponentQ() + "</DQ>");
System.out.println(" <InverseQ>" + ks.getCrtCoefficient() + "</InverseQ>");
System.out.println(" <D>" + ks.getPrivateExponent() + "</D>");
System.out.println("</RSAKeyValue>");
This will work for all RSA key pairs which internally use the 'CRT' representation, and allow export; this is the case for the key pairs that the JDK will generate by default with the code you show.
(Here I print out the key on System.out instead of writing it to a file, but you get the idea.)
Thomas Pornin's solution is essentially correct but didn't work for me because the methods, e.g. getModulus(), return BigInteger which results in a numeric string, whereas the standard .Net XML format uses Base64 encoded bytes.
I used "getModulus().toByteArray()" to get the bytes. Then I needed to trim the first element of the array (except for Exponent) because there's an unwanted zero byte. (I presume because BigInteger is signed it adds an extra byte so the leading bit can indicate sign).
I've posted the code on GitHub.
The main bit is:
static String getPrivateKeyAsXml(PrivateKey privateKey) throws Exception{
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
RSAPrivateCrtKeySpec spec = keyFactory.getKeySpec(privateKey, RSAPrivateCrtKeySpec.class);
StringBuilder sb = new StringBuilder();
sb.append("<RSAKeyValue>" + NL);
sb.append(getElement("Modulus", spec.getModulus()));
sb.append(getElement("Exponent", spec.getPublicExponent()));
sb.append(getElement("P", spec.getPrimeP()));
sb.append(getElement("Q", spec.getPrimeQ()));
sb.append(getElement("DP", spec.getPrimeExponentP()));
sb.append(getElement("DQ", spec.getPrimeExponentQ()));
sb.append(getElement("InverseQ", spec.getCrtCoefficient()));
sb.append(getElement("D", spec.getPrivateExponent()));
sb.append("</RSAKeyValue>");
return sb.toString();
}
static String getElement(String name, BigInteger bigInt) throws Exception {
byte[] bytesFromBigInt = getBytesFromBigInt(bigInt);
String elementContent = getBase64(bytesFromBigInt);
return String.format(" <%s>%s</%s>%s", name, elementContent, name, NL);
}
static byte[] getBytesFromBigInt(BigInteger bigInt){
byte[] bytes = bigInt.toByteArray();
int length = bytes.length;
// This is a bit ugly. I'm not 100% sure of this but I presume
// that as Java represents the values using BigIntegers, which are
// signed, the byte representation contains an 'extra' byte that
// contains the bit which indicates the sign.
//
// In any case, it creates arrays of 129 bytes rather than the
// expected 128 bytes. So if the array's length is odd and the
// leading byte is zero then trim the leading byte.
if(length % 2 != 0 && bytes[0] == 0) {
bytes = Arrays.copyOfRange(bytes, 1, length);
}
return bytes;
}
static String getBase64(byte[] bytes){
return Base64.getEncoder().encodeToString(bytes);
}
You can have some form of XMLObjectOutputStream such that it outputs to XML instead of a proprietary binary format as in here.
Related
I am trying to do AES Encryption using JAVA, I have made multiple attempts, tried a lot of codes and did many changes to finally reach to a place where my encrypted text matches with the encrypted text generated using C# code BUT PARTIALLY. The last block of 32 bits is different. I do not have access to the C# code since it is a 3rd Party Service. Can anyone guide what am I missing?
Conditions Mentioned are to use:
Use 256-bit AES encryption in CBC mode and with PKCS5 padding to encrypt the entire query string using your primary key and initialization vector. (Do not include a message digest in the query string.) The primary key is a 64-digit hexadecimal string and the initialization vector is a 32-digit hexadecimal string.
The sample values I used are:
Aes_IV = 50B666AADBAEDC14C3401E82CD6696D4
Aes_Key = D4612601EDAF9B0852FC0641DC2F273E0F2B9D6E85EBF3833764BF80E09DD89F (my KeyMaterial)
Plain_Text = ss=brock&pw=123456&ts=20190304234431 (input)
Encrypted_Text = 7643C7B400B9A6A2AD0FCFC40AC1B11E51A038A32C84E5560D92C0C49B3B7E0 A072AF44AADB62FA66F047EACA5C6A018 (output)
My Output =
7643C7B400B9A6A2AD0FCFC40AC1B11E51A038A32C84E5560D92C0C49B3B7E0 A38E71E5C846BAA6C31F996AB05AFD089
public static String encrypt( String keyMaterial, String unencryptedString, String ivString ) {
String encryptedString = "";
Cipher cipher;
try {
byte[] secretKey = hexStrToByteArray( keyMaterial );
SecretKey key = new SecretKeySpec( secretKey, "AES" );
cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding" );
IvParameterSpec iv;
iv = new IvParameterSpec( hexStrToByteArray( ivString ) );
cipher.init( Cipher.ENCRYPT_MODE, key, iv );
byte[] plainText = unencryptedString.getBytes( "UTF-8") ;
byte[] encryptedText = cipher.doFinal( plainText );
encryptedString = URLEncoder.encode(byteArrayToHexString( encryptedText ),"UTF-8");
}
catch( InvalidKeyException | InvalidAlgorithmParameterException | UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException e ) {
System.out.println( "Exception=" +e.toString() );
}
return encryptedString;
}
I have used this for conversions.
public static byte[] hexStrToByteArray ( String input) {
if (input == null) return null;
if (input.length() == 0) return new byte[0];
if ((input.length() % 2) != 0)
input = input + "0";
byte[] result = new byte[input.length() / 2];
for (int i = 0; i < result.length; i++) {
String byteStr = input.substring(2*i, 2*i+2);
result[i] = (byte) Integer.parseInt("0" + byteStr, 16);
}
return result;
}
public static String byteArrayToHexString(byte[] ba) {
String build = "";
for (int i = 0; i < ba.length; i++) {
build += bytesToHexString(ba[i]);
}
return build;
}
public static String bytesToHexString ( byte bt) {
String hexStr ="0123456789ABCDEF";
char ch[] = new char[2];
int value = (int) bt;
ch[0] = hexStr.charAt((value >> 4) & 0x000F);
ch[1] = hexStr.charAt(value & 0x000F);
String str = new String(ch);
return str;
}
Any Suggestions, what should I do to match the outputs?
If only the last block of ECB / CBC padding is different then you can be pretty sure that a different block cipher padding is used. To validate which padding is used you can try (as Topaco did in the comments below the question) or you can decrypt the ciphertext without padding. For Java that would be "AES/CBC/NoPadding".
So if you do that given the key (and IV) then you will get the following output in hexadecimals:
73733D62726F636B2670773D3132333435362674733D3230313930333034323334343331000000000000000000000000
Clearly this is zero padding.
Zero padding has one big disadvantage: if your ciphertext ends with a byte valued zero then this byte may be seen as padding and stripped from the result. Generally this is not a problem for plaintext consisting of an ASCII or UTF-8 string, but it may be trickier for binary output. Of course, we'll assume here that the string doesn't use a null terminator that is expected to be present in the encrypted plaintext.
There is another, smaller disadvantage: if your plaintext is exactly the block size then zero padding is non-standard enough that there are two scenarios:
the padding is always applied and required to be removed, which means that if the plaintext size is exactly a number of times the block size that still a full block of padding is added (so for AES you'd have 1..16 zero valued bytes as padding);
the padding is only applied if strictly required, which means that no padding is applied if the plaintext size is exactly a number of times the block size (so for AES you'd have 0..15 zero valued bytes as padding).
So currently, for encryption, you might have to test which one is expected / accepted. E.g. Bouncy Castle - which is available for C# and Java - always (un)pads, while the horrid PHP / mcrypt library only pads where required.
You can always perform your own padding of course, and then use "NoPadding" for Java. Remember though that you never unpad more than 16 bytes.
General warning: encryption without authentication is unfit for transport mode security.
I need to convert a RSA PublicKey into a valid JWK. Especially the values "n" an "e" of the JWK are the ones I'm struggling with. The encoding does not seem to be correct, when looking at example JWK's at https://www.rfc-editor.org/rfc/rfc7517#page-25
Currently my code looks basically like this:
private Map<String, Object> generateJWK(PublicKey publicKey){
RSAPublicKey rsa = (RSAPublicKey) publicKey;
Map<String, Object> values = new HashMap<>();
values.put("kty", rsa.getAlgorithm()); // getAlgorithm() returns kty not algorithm
values.put("kid", "someuniqueid");
values.put("n", Base64.encode(rsa.getModulus().toString()));
values.put("e", Base64.encode(rsa.getPublicExponent().toString()));
values.put("alg", "RS256");
values.put("use", "sig");
return values;
}
The output however does not seem to be correctly encoded or something, e for example looks like this: NjU1Mzc=
n does not include special characters like -, _ and + . :
jMzk1MNT0xTk2NED1xzzgNyQ00IykADzMAM0c0wz0M0MONj2z5TgNzI3yAM0OONYzzMjzwNzDxgAzxDxzMMAjTwNNDYMINMgNQDOEAkIM2jMQzkjUTDUYONNg1A00Tw1Nx4YEzAzjUT1MTNMjDjMM1MNNAjyTMIzxNADDINQANwT5yTDEMjEzNz2z2gOgjDDDNyNDjTzz43ETOYMI35gDjE00MYYM2DzDjDgww53Mwz0ME1NMgOM3MIzYTzMwzOMIQU5MjOzUjMNQNNg50U5NIDNzw2DMMOggNcQQM21TI5NMzDTN5Mj123O33MNNMkyNTNONxMM5wMMc04jTgAUE3MM1zMg4NNMT4MNDMM5yTO2j4jNDEMy1yNANNAzOIEUDzNwzExwTIkNjUjkN54Uz0DT5x0zM51k2MxYkx0zMNzxMkDUDTTQN3gAYODATQDDwMDMjMMcONjxMNTYMT5kgxNkMjNMQU0jzMEwIIMzTzUD4MgYDkDNzcAzN0TN4yNTz11DMxDUjDM2MyDMy4DEINMwT22QxjNNEzNDATy1OM1NNDxYgz5TxDkj3gQ32kIwNNkDO3xczDAENcTMNO0MOjTDwE3g11wNUcgNTwQk30kjjNNzTz4jTj4OOjQNYzMzcMjTQMkyzNNNUQOTOMMkMMMNzwNxDOEkg4xADIT4DNxMz2TENT4yN4z2I2zjyMU3DTOEQN4MIQjNDMU5Y11QkccwMNI0kNzyNjMMN4NMTTNMzMwxMjjDzgAANO1zwjYIEUjM1ADgDNjxTITMNNkIYxzyzzEEDMzDzNjzM4NjNNjc3ITTD0T5jzN=
Am I assuming right that both values n and e are not properly encoded?
How should I convert the PublicKey to JWK? (Can not use 3rd party libraries)
JWK uses base64url encoding which is slighly different to base64. Additionally, do not use toString() method on BigInteger values. Get directly the data as byte array
Change
Base64.encode(rsa.getModulus().toString())
Base64.encode(rsa.getPublicExponent().toString())
To
Base64.getUrlEncoder().encodeToString(rsa.getModulus().toByteArray())
Base64.getUrlEncoder().encodeToString(rsa.getPublicExponent().toByteArray())
code below works fine, use Apache Base64, search by phrases
RFC 4648 vs RFC 2045 codec difference
and
java biginteger to byte array leading zero
private static byte[] toByteArray(BigInteger bigInteger)
{
byte[] bytes = bigInteger.toByteArray();
byte[] result;
if ((bigInteger.bitLength() % 8 == 0) && (bytes[0] == 0) && bytes.length > 1)
{
result = new byte[bytes.length - 1];
System.arraycopy(bytes, 1, result, 0, result.length);
}
else {result = bytes;}
return result;
}
...
import org.apache.commons.codec.binary.Base64;
...
jwk.put("n", Base64.encodeBase64URLSafeString(toByteArray(publicKey.getModulus()) ) );
jwk.put("e", Base64.encodeBase64URLSafeString(publicKey.getPublicExponent().toByteArray()) );
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;
}
I have to write client provided Ruby code in Java. The code uses secret key and Base64 encoding to form hmac value. I tried to write similar code in Java but the resulted hmac value is not matching with the Ruby script result. Please find the below block of code for Java & Ruby along with resulted output.
Java Code:
public static void main(String[] args)
throws NoSuchAlgorithmException, InvalidKeyException
{
// get an hmac_sha1 key from the raw key bytes
String secretKey =
"Ye2oSnu1NjzJar1z2aaL68Zj+64FsRM1kj7I0mK3WJc2HsRRcGviXZ6B4W+/V2wFcu78r8ZkT8=";
byte[] secretkeyByte = Base64.decodeBase64(secretKey.getBytes());
SecretKeySpec signingKey = new SecretKeySpec(secretkeyByte, "HmacSHA1");
// get an hmac_sha1 Mac instance and initialize with the signing key.
String movingFact = "0";
byte[] text = movingFact.getBytes();
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
// compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(text);
byte[] hash = Base64.encodeBase64(rawHmac);
System.out.println("hash :" + hash);
}
Java Output: hash :[B#72a32604
Ruby Code:
def get_signature()
key = Base64.decode64("Ye2oSnu1NjzJar1z2aaL68Zj+64FsRM1kj7I0mK3WJc2HsRRcGviXZ6B4W+/V2wFcu78r8ZkT8=")
digest = OpenSSL::Digest::Digest.new('sha1')
string_to_sign = "0"
hash = Base64.encode64(OpenSSL::HMAC.digest(digest, key, string_to_sign))
puts "hash: " + hash
end
Ruby Output: hash: Nxe7tOBsbxLpsrqUJjncrPFI50E=
As mentionned in the comments, you're printing the description of your byte array, not the contents:
Replace:
System.out.println("hash :" + hash);
With:
System.out.println("hash: " + new String(hash, StandardCharsets.UTF_8));
public static byte[] decryptByte(byte[] blahh, byte[] keyExample) throws Exception
{
Cipher cipher = null;
try
{
cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(Base64.decodeBase64(blah));
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}
String keyExample = "99112277445566778899AABBCCDDEEFF0123456789ABCDEF0123456789ABCDEF";
byte[] key = keyExample.getBytes();
byte[] barrayMessage = {123,45,55,23,64,21,65};
byte[] result = decryptByte(barrayMessage, key);
Exception thrown: java.security.InvalidKeyException: Invalid AES key length: 64 bytes
When you call String.getBytes() (JDK documentation) you encodes characters of the given string into a sequence of bytes using the platform's default charset.
What you are actually need to do is to convert each hexadecimal (also base 16) number (represented by two characters from 0 to 9 and A to F e.g. 1A, 99, etc.) into its corresponding numerical (byte) value e.g. "FF" -> -1 byte.
Sample code is as follows:
import static java.lang.Character.digit;
...
private static byte[] stringToBytes(String input) {
int length = input.length();
byte[] output = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
output[i / 2] = (byte) ((digit(input.charAt(i), 16) << 4) | digit(input.charAt(i+1), 16));
}
return output;
}
...
String keyExample = "99112277445566778899AABBCCDDEEFF0123456789ABCDEF0123456789ABCDEF";
byte[] key = stringToBytes(keyExample);
byte[] barrayMessage = {123,45,55,23,64,21,65};
byte[] result = decryptByte(barrayMessage, key);
Please bear in mind that because we convert each two characters into a single byte, the proposed method assumes your input will have even number of characters (also the input is not null and empty).
If that method is going to be used internally that form is acceptable but if you make it as a part of library visible to others, it would be good to put some checks and throw exception on invalid input.
You should try and decode your key using a hexadecimal decoder instead of calling getBytes().