Good day.
I'm trying to implement RSA encryption between server and Android app. My goal is:
Share the public key with the app.
The application performs encryption and sends the encrypted text to the server.
The server decrypts the text with the private key.
Before the Android app, I did the same steps but with a Java Application, and it worked like a charm. But the thing is how the client was made in that application... On the server side, I did a Soap web service that returns the array of bytes of the public key:
#WebMethod(operationName = "getKey")
public byte[] getPublicKey() {
try {
// Message Context
MessageContext mctx = wsctx.getMessageContext();
// Getting parameter services
ParameterServices params = ParameterServices.NewParameterServices(getHttpServletRequestFromMsgContext(mctx), "Dumb Payload");
// Initializing KEYGEN Object
initKeyGen(params);
// Checking if there are no keys created
KEYGEN.updateKeys();
// Creating and sending public key
return KEYGEN.generatePublicKey();
} catch (Throwable e) {
LOGGER.error("Exception in KeyService", e);
return new byte[0];
}
}
In the Java Application, I created the client Interface with javax.jws like this:
#WebService(targetNamespace = "http://my.targetname.value/")
#SOAPBinding(style = SOAPBinding.Style.RPC)
public interface KeyService {
#WebMethod
byte[] getPublicKey();
}
And this is the part where I'm pretty sure that is the problem. How I retrieve the array of bytes to make the public key it´s something like this:
try {
URL url = new URL(WSDLURL);
QName qname =
new QName(TARGETNAMESPACE, SERVICENAME);
Service service = Service.create(url, qname);
KeyService keyserv = service.<KeyService>getPort(KeyService.class);
byte[] key = keyserv.getPublicKey();
X509EncodedKeySpec spec = new X509EncodedKeySpec(key);
KeyFactory kf = KeyFactory.getInstance("RSA");
publicKey = kf.generatePublic(spec);
return true;
} catch (MalformedURLException|java.security.NoSuchAlgorithmException|java.security.spec.InvalidKeySpecException e) {
e.printStackTrace();
return false;
}
This line:
byte[] key = keyserv.getPublicKey();
I'm not doing anything with the bytes, I'm just fetching it and done, whatever is throwing the method java.security.Key.getEncoded() from the server.
ANDROID KOTLIN VERSION
First of all, I tried to import JAXB to android, and I died during the attempt. Later I found the android library ksoap2 from this question. The implementation's almost the same, also the dependencies (ksoap2-android-2.5.2, kxml2-2.2.1...)
class KeyServiceKSoap {
val NAMESPACE = "http://my.targetname.value/"
val URL = "http://192.168.0.11:8080/RSATest/keyService?wsdl"
val METHOD_NAME = "obtainKey"
val SOAP_ACTION = "http://my.targetname.value/RSATest/obtainKeyRequest"
private var thread: Thread? = null
fun fetchByteArray(getKey : MutableLiveData<String>) {
thread = object : Thread() {
override fun run() {
try {
Log.d("KeyService", "Starting...")
val request = SoapObject(NAMESPACE, METHOD_NAME)
val envelope = SoapSerializationEnvelope(
SoapEnvelope.VER12
)
envelope.env = "http://schemas.xmlsoap.org/soap/envelope/"
envelope.setOutputSoapObject(request)
envelope.headerOut = Array(1) { buildAuthHeaders() }
val androidHttpTransport = HttpTransportSE(
URL,
30000
)
androidHttpTransport.call(SOAP_ACTION, envelope)
val objectResult: SoapObject = envelope.bodyIn as SoapObject
getKey.postValue(objectResult.getProperty("return").toString())
} catch (sp: SoapFault) {
sp.printStackTrace()
getKey.postValue("FAILURE")
} catch (e: Exception) {
e.printStackTrace()
getKey.postValue("FAILURE")
}
}
}
thread!!.start()
}
private fun buildAuthHeaders() : Element {
val authorization = Element().createElement(NAMESPACE, "Authorization")
authorization.addChild(Node.TEXT, "BEARER")
return authorization
}
}
THE PROBLEM
My problem here is that the response I'm getting it as a String, as you can see in this line:
objectResult.getProperty("return").toString()
If you check the Java Application client, I'm getting the array value directly, and not as a String. The issue here is that I don't know what kind of encoding is doing the method java.security.Key.getEncoded(). I mean, If I know for example that this method is returning a Base64 encoded byte array, in the android application I just need to decode like this and done:
private fun makeKey(encodedString : String) : Boolean {
return try {
val byteArr = Base64.decode(encodedString, Base64.DEFAULT);
val specifications = X509EncodedKeySpec(byteArr)
val factory: KeyFactory = KeyFactory.getInstance("RSA")
publicKey = factory.generatePublic(specifications)
true
} catch (e : Exception) {
e.printStackTrace()
false
}
}
BUT OBVIOUSLY IS NOT LIKE THIS, if I encrypt and send the encoded string to the server, and the server tries to decrypt this string, I will get javax.crypto.BadPaddingException: Decryption error, since the array of bytes was not encoded with Base64...
Is there a way to receive the byte array in ksoap2 Android like the interface created in the Java Application?
If there is no choice and I have to decode the String (to make the RSA public key in the android app)... what would be the right way to do it?
I write some code for you, and you can see the complete code at https://github.com/XSComSoft/stack-overflow
In the main and two copies of the test code package org.exmaple.Stack74979278 respectively containing the client code and the server code
You can simple run test at class org.example.stack74979278.Main if you open this with idea as maven project
Communication only needs to rely on java jws
Here are some code snippets
KeyService.java
#WebService
#SOAPBinding(style = SOAPBinding.Style.RPC)
public interface KeyService {
#WebMethod
#WebResult(name = "get")
byte[] getPublicKey();
#WebMethod
#WebResult(name = "get")
String getDecodeByPrivateKey(String base64) throws Exception;
}
KeyServiceImpl.java
#WebService(endpointInterface = "org.example.stack74979278.KeyService")
public class KeyServiceImpl implements KeyService{
private RSAPublicKey puk;
private RSAPrivateKey prk;
public byte[] getPublicKey() {
return puk.getEncoded();
}
#Override
public String getDecodeByPrivateKey(String base64) throws Exception {
return RSAUtils.decryptByPrivateKey(base64, prk);
}
public KeyServiceImpl() throws Exception {
// List<Key> keyList = RSAUtils.getRSAKeyObject(1024);
// puk = (RSAPublicKey) keyList.get(0);
// prk = (RSAPrivateKey) keyList.get(1);
Properties properties = new Properties();
properties.load(KeyServiceImpl.class.getResourceAsStream("/stack74979278/keys.properties"));
puk = RSAUtils.getPublicKey(properties.getProperty("public"));
prk = RSAUtils.getPrivateKey(properties.getProperty("private"));
String publicKey = Base64.getEncoder().encodeToString(puk.getEncoded());
String privateKey = Base64.getEncoder().encodeToString(prk.getEncoded());
System.out.println("publicKey");
System.out.println(publicKey);
System.out.println("privateKey");
System.out.println(privateKey);
}
public static void main(String[] args) throws Exception {
Endpoint.publish("http://localhost/test", new KeyServiceImpl());
}
}
RSAUtil.java
public class RSAUtils {
// 加密算法
private final static String ALGORITHM_RSA = "RSA";
/**
* 直接生成公钥、私钥对象
*
* #param modulus
*
* #throws NoSuchAlgorithmException
*
*/
public static List<Key> getRSAKeyObject(int modulus) throws NoSuchAlgorithmException{
List<Key> keyList = new ArrayList<>(2);
// 创建RSA密钥生成器
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(ALGORITHM_RSA);
// 设置密钥的大小,此处是RSA算法的模长 = 最大加密数据的大小
keyPairGen.initialize(modulus);
KeyPair keyPair = keyPairGen.generateKeyPair();
// keyPair.getPublic() 生成的是RSAPublic的是咧
keyList.add(keyPair.getPublic());
// keyPair.getPrivate() 生成的是RSAPrivateKey的实例
keyList.add(keyPair.getPrivate());
return keyList;
}
/**
* 生成公钥、私钥的字符串
* 方便传输
*
* #param modulus 模长
* #return
* #throws NoSuchAlgorithmException
*/
public static List<String> getRSAKeyString(int modulus) throws NoSuchAlgorithmException{
List<String> keyList = new ArrayList<>(2);
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(ALGORITHM_RSA);
keyPairGen.initialize(modulus);
KeyPair keyPair = keyPairGen.generateKeyPair();
String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
keyList.add(publicKey);
keyList.add(privateKey);
return keyList;
}
public static RSAPublicKey getPublicKey(String publicKey) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
return (RSAPublicKey) keyFactory.generatePublic(spec);
}
public static RSAPublicKey getPublicKey(byte[] keyBytes) throws Exception {
RSAPublicKey publicKey =
(RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(keyBytes));
return publicKey;
}
public static RSAPrivateKey getPrivateKey(String privateKey) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKey);
return getPrivateKey(keyBytes);
}
public static RSAPrivateKey getPrivateKey(byte[] keyBytes) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
return (RSAPrivateKey) keyFactory.generatePrivate(spec);
}
/**
* 公钥加密
*
* #param data
* #param publicKey
* #return
* #throws Exception
*/
public static String encryptByPublicKey(String data, RSAPublicKey publicKey)
throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 模长n转换成字节数
int modulusSize = publicKey.getModulus().bitLength() / 8;
// PKCS Padding长度为11字节,所以实际要加密的数据不能要 - 11byte
int maxSingleSize = modulusSize - 11;
// 切分字节数组,每段不大于maxSingleSize
byte[][] dataArray = splitArray(data.getBytes(), maxSingleSize);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 分组加密,并将加密后的内容写入输出字节流
for (byte[] s : dataArray) {
out.write(cipher.doFinal(s));
}
// 使用Base64将字节数组转换String类型
return Base64.getEncoder().encodeToString(out.toByteArray());
}
/**
* 私钥解密
*
* #param data
* #param privateKey
* #return
* #throws Exception
*/
public static String decryptByPrivateKey(String data, RSAPrivateKey privateKey)
throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// RSA加密算法的模长 n
int modulusSize = privateKey.getModulus().bitLength() / 8;
byte[] dataBytes = data.getBytes();
// 之前加密的时候做了转码,此处需要使用Base64进行解码
byte[] decodeData = Base64.getDecoder().decode(dataBytes);
// 切分字节数组,每段不大于modulusSize
byte[][] splitArrays = splitArray(decodeData, modulusSize);
ByteArrayOutputStream out = new ByteArrayOutputStream();
for(byte[] arr : splitArrays){
out.write(cipher.doFinal(arr));
}
return new String(out.toByteArray());
}
/**
* 按指定长度切分数组
*
* #param data
* #param len 单个字节数组长度
* #return
*/
private static byte[][] splitArray(byte[] data,int len){
int dataLen = data.length;
if (dataLen <= len) {
return new byte[][]{data};
}
byte[][] result = new byte[(dataLen-1)/len + 1][];
int resultLen = result.length;
for (int i = 0; i < resultLen; i++) {
if (i == resultLen - 1) {
int slen = dataLen - len * i;
byte[] single = new byte[slen];
System.arraycopy(data, len * i, single, 0, slen);
result[i] = single;
break;
}
byte[] single = new byte[len];
System.arraycopy(data, len * i, single, 0, len);
result[i] = single;
}
return result;
}
}
and the client generator by cmdwsimport -keep -p org.example.stack74979278 http://localhost/test?wsdl, see detail a
the client test class
#Log
public class Main {
#Test
public void getPublicKey(){
byte[] bytes = new KeyServiceImplService().getKeyServiceImplPort().getPublicKey();
System.out.println(bytes);
}
#Test
public void getDecode() throws Exception {
KeyService keyService = new KeyServiceImplService().getKeyServiceImplPort();
byte[] bytes = keyService.getPublicKey();
RSAPublicKey publicKey = RSAUtils.getPublicKey(bytes);
String encrypt = "The message encrypt";
String str = keyService.getDecodeByPrivateKey(RSAUtils.encryptByPublicKey(encrypt, publicKey));
log.info(str);
}
}
Now you can get the public key byte for android client and send server the encrypted base64 string
Well I don't know how to start this. Apparently, the way to receive an array of bytes as I was doing right now, is the right one.
With ksoap2 you get the response string, and then decode the String to get your array of bytes.
private fun makeKey(encodedString : String) : Boolean {
return try {
val byteArr = Base64.decode(encodedString, Base64.DEFAULT) <--- OK
val specifications = X509EncodedKeySpec(byteArr)
val factory: KeyFactory = KeyFactory.getInstance("RSA")
publicKey = factory.generatePublic(specifications)
true
} catch (e : Exception) {
e.printStackTrace()
false
}
}
How come I´m so sure of this? Well, is because what was killing me had nothing to do with encoding. It was how I was initializing the Cipher. As I said before, with Java applications, everything was working correctly, but when using the same implementation in android applications (Java Application <=> Android Application), the decryption failed.
How I was initializing the Cipher was like this:
Cipher.getInstance("RSA")
According with a comment of this question If you initialize it this way, what will happen is that both, the Android App and the Server will use their independent implementations. This is why it was always failing at the moment of decryption.
To make all things clear:
If you are trying to do the same as me, try to initialize all Cipher with RSA/ECB/PKCS1Padding in both sides (Server and App)
Cipher.getInstance("RSA/ECB/PKCS1Padding")
Other things like KeyFactory and KeyPairGenerator has to be initialized only with "RSA".
KeyPairGenerator.getInstance("RSA")
KeyFactory.getInstance("RSA")
P.D: I NEVER encoded the array of bytes of the key to Base64 in my SOAP Service... Which makes me think that base64 encoding is done by default at some point
Related
I am generating a public and private key pair in javascript. The public key is being sent to the server where I have java code that encrypts the string. When I try to decrypt the string in Javascript using a private key I get an error. Here is the Javascript code I used.
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class CryptographyService {
private publicKey: string;
private privateKey: string;
public randomKey: string;
private keyPair: CryptoKeyPair;
public exportedKey: string;
constructor() {
/**Generate the public and private key */
this.generateKeys()
.then(() => {
return this.exportKey(this.keyPair.publicKey);
})
.then(() => {});
}
public setPublicKey(key: string) {
this.publicKey = key;
}
public setRandomKey(key: string) {
this.randomKey = key;
}
public setPrivateKey(key: string) {
this.privateKey = key;
}
public async encryptMessage(text: String) {
const { default: NodeRSA } = await import('node-rsa');
const key = NodeRSA(this.publicKey, 'pkcs8-public');
return key.encrypt(text, 'base64');
}
// For subscription key encryption and decryption
private async generateKeys() {
const keyPair = await crypto.subtle.generateKey(
{
name: 'RSA-OAEP', //algorithm
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256'
},
true,
['encrypt', 'decrypt']
);
this.keyPair = keyPair; /**{
publicKey:
privateKey:
} */
}
private async exportKey(key: CryptoKey) {
/** exportKey() it takes as input a CryptoKey object and gives you the key in an external, portable format.
* crypto.subtle.exportKey(format, key); : returns a promise
* spki format is used to import/export RSA or Elliptic Curve public keys.
*/
const exported = await crypto.subtle.exportKey('spki', key); //Returns an ArrayBuffer
const exportedAsString = this.ab2str(exported); // Converts ArrayBuffer to String
/**btoa encodes a string to base64 */
const exportedAsBase64 = window.btoa(exportedAsString);
this.exportedKey = exportedAsBase64;
}
// Uses private key to decrypt message sent from the backend
public async decryptMessage(input: string) {
const ciphertext = this.str2ab(input);
const decrypted = await window.crypto.subtle.decrypt(
{
name: 'RSA-OAEP'
},
this.keyPair.privateKey,
ciphertext
);
const dec = new TextDecoder();
const decodedMessage = dec.decode(decrypted);
return decodedMessage;
}
private ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
private str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
}
The java code to convert the string to PublicKey Object
String pubKey = loginRequest.publicKey;
PublicKey pk = null;
try {
byte[] keyBytes = Base64.decodeBase64(pubKey);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
pk = kf.generatePublic(spec);
} catch (Exception e) {
System.out.println("Exception in generating primary key: " + e.getMessage());
}
Encrypting the string with the publicKey
public static byte[] encrypt(String text, PublicKey key) {
byte[] cipherText = null;
try {
// get an RSA cipher object and print the provider
final Cipher cipher = Cipher.getInstance(ALGORITHM);
OAEPParameterSpec oaepParams = new OAEPParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), PSource.PSpecified.DEFAULT);
// encrypt the plain text using the public key
cipher.init(Cipher.ENCRYPT_MODE, key, oaepParams);
cipherText = cipher.doFinal(text.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
return cipherText;
}
I get back an encrypted string when when I try to decode it in Javascript I get an error. Any idea what I need to change in my code to fix this?
I am trying to port my working python code for generating signature to java but they both are producing different signatures. Here is my code for python which is generating the signature.
import pdb
pdb.set_trace()
signer = PKCS1_v1_5.new(priv_key)
digest = SHA256.new()
digest.update(message)
val = signer.sign(digest)
return val
I am calling this function using this statement
signature = b64encode(sign(msg1, private))
Here the msg1 is
msg1 = 'test'.encode("utf8")
and private is the private key which I am importing using
RSA.importKey("<Location of key>"
I am trying to write a similar java code for implementing the same functionality with this given code
public static void main(String[] args) throws Exception {
String payload = "test";
String dig = makeDigest( payload, "SHA-256");
Key k = getPrivateKey("private_key.der");
String signature = encrypt(dig, "RSA", k);
System.out.print(signature);
}
public static String makeDigest(String payload, String digestAlgo) {
String strDigest = "";
try {
MessageDigest md = MessageDigest.getInstance(digestAlgo);
byte[] p_b = payload.getBytes("UTF-8");
md.update(p_b);
byte[] digest = md.digest();
char[] encoded = Hex.encodeHex(digest);
strDigest = new String(encoded);
System.out.println(strDigest);
}
catch (Exception ex) {
ex.printStackTrace();
}
return strDigest;
}
public static String encrypt(String sha, String encryptAlgo, Key k) {
String strEncrypted = "";
try {
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance(encryptAlgo,"BC");
cipher.init(Cipher.ENCRYPT_MODE, k);
byte[] encrypted = cipher.doFinal(sha.getBytes("UTF-8"));
//System.out.println(new String(encrypted));
byte[] encoded = Base64.getEncoder().encode(encrypted);
strEncrypted = new String(encoded);
}
catch (Exception ex) {
ex.printStackTrace();
}
return strEncrypted;
}
public static PrivateKey getPrivateKey(String filename)
throws Exception {
byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
Security.addProvider(new BouncyCastleProvider());
KeyFactory kf = KeyFactory.getInstance("RSA","BC");
return kf.generatePrivate(spec);
}
Both thee codes are generating different signatures. I feel something I am doing wrong in selecting the algorithm on Java side. I have tried with
RSA/NONE/PKCS1Padding
and
RSA/ECB/PKCS1Padding
but still, it is different signature as compared to python code.
Any lead on this will be very helpful.
I have a requirement in which I have to communicate with a
Web Service by sending three parameters that are encrypted: ID, key and initialization vector (IV). In order to do this, I need to use Triple DES (3DES/DESede) encryption as well as RSA encryption. The steps required to do this are:
Start with the ID in clear text. Decode the ID from a base64 string to an array of bytes. The original ID is not base64 encoded but this step must be performed.
Encrypt the base64 decoded ID using 3DES and generate the key and IV
Encrypt the key and IV generated with RSA (using a certificate provided)
Convert encrypted ID, encrypted key and IV to base64 strings.
I was provided with a C# example on how to do this and the code looks as displayed below:
namespace SampleEncryption
{
public class SampleWebServiceInvocation
{
private const string CERT_SUBJECT = "sample.cer";
public string GetReport()
{
ReportService.RService ws = new ReportService.RService();
ReportService.ReportRequest req = new ReportService.ReportRequest();
string key = string.Empty;
string iv = string.Empty;
string id = "1234567890123456";
string encrypt = new CryptEx().EncryptEx(id, CERT_SUBJECT, ref key, ref iv);
req.AccountNumber = encrypt;
req.Key = key;
req.InitializationVector = iv;
ReportService.ReportResponse resp = ws.getReport(req);
if (resp.Success)
return resp.RedirectUrl;
return string.Empty;
}
}
/// <summary>
/// CryptorEx
/// </summary>
public class CryptEx
{
private X509Certificate2 _certificate;
private byte[] _encryptedKey;
private byte[] _encryptedIV;
private byte[] _encryptedText;
private byte[] _decryptedKey;
private byte[] _decryptedIV;
private byte[] _decryptedText;
/// <summary>
/// Default (empty) constructor
/// </summary>
public CryptEx()
{
}
/// <summary>
/// Publicly exposed encrypt method
/// </summary>
public string EncryptEx(string decryptedText, string certSubject, ref string key, ref string iv)
{
string data;
_decryptedText = Convert.FromBase64String(decryptedText);
try
{
_certificate = GetSignerCert(certSubject);
_encryptedText = EncryptWithTripleDES();
EncryptWithRSA();
key = Convert.ToBase64String(_encryptedKey);
iv = Convert.ToBase64String(_encryptedIV);
data = Convert.ToBase64String(_encryptedText);
}
catch (Exception)
{
data = string.Empty;
}
return data;
}
private byte[] EncryptWithTripleDES()
{
//Create a crypto provider for the text
using (TripleDESCryptoServiceProvider tripDes = new TripleDESCryptoServiceProvider())
{
tripDes.Mode = CipherMode.CBC;
ICryptoTransform encryptor = tripDes.CreateEncryptor();
//Encrypt
using (MemoryStream ms = new MemoryStream())
{
CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);
cs.Write(_decryptedText, 0, _decryptedText.Length);
cs.FlushFinalBlock();
cs.Close();
ms.Close();
_decryptedKey = tripDes.Key;
_decryptedIV = tripDes.IV;
return ms.ToArray();
}
}
}
private void EncryptWithRSA()
{
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)_certificate.PublicKey.Key)
{
_encryptedKey = rsa.Encrypt(_decryptedKey, false);
_encryptedIV = rsa.Encrypt(_decryptedIV, false);
}
}
private static X509Certificate2 GetSignerCert(string certName)
{
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
try
{
store.Open(OpenFlags.ReadOnly);
// First, find certificates that haven't expired
X509Certificate2Collection validCerts = store.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
X509Certificate2Collection scorecardCerts = validCerts.Find(X509FindType.FindBySubjectName, certName, false);
if (scorecardCerts.Count == 0)
throw new ApplicationException(string.Format("Unable to find certificate with name of {0}", certName));
if (scorecardCerts.Count > 1)
throw new ApplicationException(string.Format("More than one certificate has a name of {0}\r\n{1}",
certName, GetSubjectNames(scorecardCerts)));
return scorecardCerts[0];
}
finally
{
store.Close();
}
}
private static string GetSubjectNames(X509Certificate2Collection signingCert)
{
StringBuilder sb = new StringBuilder();
foreach (X509Certificate2 cert in signingCert)
sb.AppendLine(cert.Subject);
return sb.ToString();
}
}
}
In my case, I need to do this but in Java, so here I expose the logic of my implementation.
For Triple DES:
public class DESedeCrypto implements SymmetricCryptoManager {
/**
* Constant for the algorithm being used for Triple DES (CBC)
*/
public static final String DESEDE_CBC_TRANSFORMATION_ALG = "DESede/CBC/NoPadding";
#Override
public DESedeEncryptionResult encrypt(byte[] srcData, String cryptoAlgorithm) throws Exception {
DESedeEncryptionResult result = null;
byte[] encryptedBytes = null;
byte[] initializationVector = null;
if (srcData == null || srcData.length == 0) {
throw new InvalidEncryptionTargetException();
}
KeyGenerator keyGen = KeyGenerator.getInstance("DESede");
SecretKey secretKey = keyGen.generateKey();
Cipher cipher = Cipher.getInstance(cryptoAlgorithm);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
result = new DESedeEncryptionResult();
if (cryptoAlgorithm.equals(DESEDE_CBC_TRANSFORMATION_ALG)) {
IvParameterSpec spec = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
initializationVector = spec.getIV();
result.setInitializationVector(initializationVector);
}
encryptedBytes = cipher.doFinal(srcData);
result.setResultBytes(encryptedBytes);
result.setKeyBytes(secretKey.getEncoded());
return result;
}
}
For RSA:
public class RSACrypto implements AsymmetricCryptoManager {
/**
* Tranformation algorithm for OAEP.
*/
private static final String OAEP_RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
/**
* Constructor of the class.
*/
public RSACrypto() {
}
#Override
public byte[] encryptWithCertificate(InputStream inputStream, byte[] targetBytes) throws Exception {
byte[] encryptedBytes = null;
if (targetBytes == null || targetBytes.length == 0) {
throw new InvalidEncryptionTargetException();
}
if (inputStream == null) {
throw new InvalidCertificatePathException("Resource specified for operation is not valid");
}
X509Certificate certificate = CryptoUtils.readCertificateFromInputStream(inputStream);
encryptedBytes = getEncryptedBytes(certificate, targetBytes);
return encryptedBytes;
}
private byte[] getEncryptedBytes(X509Certificate certificate, byte[] targetBytes) throws Exception {
byte[] encryptedBytes = null;
PublicKey publicKey = certificate.getPublicKey();
Cipher cipher = Cipher.getInstance(OAEP_RSA_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedBytes = cipher.doFinal(targetBytes);
return encryptedBytes;
}
}
Here's how I read the certificate:
public static X509Certificate readCertificateFromInputStream(InputStream inputStream) throws Exception {
X509Certificate certificate = null;
CertificateFactory certFactory = CertificateFactory.getInstance(X509_CERT_TYPE);
certificate = (X509Certificate) certFactory.generateCertificate(inputStream);
return certificate;
}
And here is the logic of the whole encryption process:
String base64id = new String(CryptoUtils.encodeBase64(base64id.getBytes()));
DESedeEncryptionResult encryptionResult = desedeCrypto.encrypt(CryptoUtils.decodeBase64(base64id), DESedeCrypto.DESEDE_CBC_TRANSFORMATION_ALG);
byte[] rsaEncryptedKey = rsaCrypto.encryptWithCertificate(certForKeyInputStream, encryptionResult.getKeyBytes());
byte[] rsaEncryptedIv = rsaCrypto.encryptWithCertificate(certForIvInputStream, encryptionResult.getInitializationVector());
String encryptedId = CryptoUtils.getBase64EncodedStr(encryptionResult.getResultBytes());
String finalEncryptedKey = CryptoUtils.getBase64EncodedStr(rsaEncryptedKey);
String finalIv = CryptoUtils.getBase64EncodedStr(rsaEncryptedIv);
The ID used for testing is: 1234567890123456
When I send the results to the Web Service I receive a message that the encryption cannot be done. In order to make sure, I tested both implementations (Java and c#). C# generated values are working when I send them to the web service, but Java implementation is not working.
Do you know if there's some specific type of algorithm I'd need to add to the Java implementation, I mean in Triple DES we use DESede/CBC/NoPadding but maybe c# uses another. Same for RSA.
Maybe somebody can give a me clue or recommendation regarding this? Thanks in advance.
I'm implementing in Java a third party application, but some part of the application I'm get a encrypt string like this: eGlhV2xNbmdqSFBkbEhkZDNpZ3gwQT09
and have to decrypt.
The guy who code this application is no longer here, so I need some help make a decrypt code in Java.
this is the password :CB=Z8#P#0!N2/8$%3K-9C(5S9*FDH+0Z
public static class SystemCriptografia
{
#region Atributos
private static string chave = "CB=Z8#P#0!N2/8$%3K-9C(5S9*FDH+0Z";
private static SymmetricAlgorithm algoritmo = new RijndaelManaged();
#endregion
#region Métodos
#region Métodos privados
private static string base64Encode(string data)
{
byte[] encData_byte = new byte[data.Length];
encData_byte = System.Text.Encoding.UTF8.GetBytes(data);
string encodedData = Convert.ToBase64String(encData_byte);
return encodedData;
}
private static string base64Decode(string data)
{
UTF8Encoding encoder = new UTF8Encoding();
Decoder utf8Decode = encoder.GetDecoder();
byte[] todecode_byte = Convert.FromBase64String(data);
int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);
char[] decoded_char = new char[charCount];
utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0);
string result = new String(decoded_char);
return result;
}
private static string Criptografa(string valor, string chave)
{
byte[] ByteValor = Encoding.UTF8.GetBytes(valor);
// Seta a chave privada
algoritmo.Mode = CipherMode.CBC;
algoritmo.Key = Encoding.Default.GetBytes(chave);
algoritmo.IV = Encoding.Default.GetBytes("brasilshopsoft07");
// Interface de criptografia / Cria objeto de criptografia
ICryptoTransform cryptoTransform = algoritmo.CreateEncryptor();
MemoryStream _memoryStream = new MemoryStream();
CryptoStream _cryptoStream = new CryptoStream(_memoryStream, cryptoTransform, CryptoStreamMode.Write);
// Grava os dados criptografados no MemoryStream
_cryptoStream.Write(ByteValor, 0, ByteValor.Length);
_cryptoStream.FlushFinalBlock();
// Busca o tamanho dos bytes encriptados
byte[] cryptoByte = _memoryStream.ToArray();
// Converte para a base 64 string para uso posterior em um xml
return Convert.ToBase64String(cryptoByte, 0, cryptoByte.GetLength(0));
}
private static string Descriptografa(string valor, string chave)
{
// Converte a base 64 string em num array de bytes
byte[] cryptoByte = Convert.FromBase64String(valor);
// Seta a chave privada
algoritmo.Mode = CipherMode.CBC;
algoritmo.Key = Encoding.Default.GetBytes(chave);
algoritmo.IV = Encoding.Default.GetBytes("brasilshopsoft07");
// Interface de criptografia / Cria objeto de descriptografia
ICryptoTransform cryptoTransform = algoritmo.CreateDecryptor();
MemoryStream _memoryStream = new MemoryStream(cryptoByte, 0, cryptoByte.Length);
CryptoStream _cryptoStream = new CryptoStream(_memoryStream, cryptoTransform, CryptoStreamMode.Read);
// Busca resultado do CryptoStream
StreamReader _streamReader = new StreamReader(_cryptoStream);
return _streamReader.ReadToEnd();
}
#endregion
public static string ToCriptografa(this string valor)
{
return Criptografa(valor, chave);
}
public static string ToDescriptografa(this string valor)
{
return Descriptografa(valor, chave);
}
public static string ToCriptografaQueryString(this string valor)
{
return base64Encode(Criptografa(valor, chave));
}
public static string ToDescriptografaQueryString(this string valor)
{
return Descriptografa(base64Decode(valor), chave);
}
#endregion
}
and this is the java code that I'm trying to do :
public class Criptografia {
private static final String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5PADDING";
private static final int KEY_SIZE = 256;
public static void main(final String[] args) throws Exception {
System.out.println(decryptAuthorizationString(
"eGlhV2xNbmdqSFBkbEhkZDNpZ3gwQT09", "CB=Z8#P#0!N2/8$%3K-9C(5S9*FDH+0Z"));
}
private static String decryptAuthorizationString(final String authString,
final String password) {
try {
// --- check if AES-256 is available
if (Cipher.getMaxAllowedKeyLength(AES_CBC_PKCS5PADDING) < KEY_SIZE) {
throw new IllegalStateException("Unlimited crypto files not present in this JRE");
}
// --- create cipher
final Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);
// --- create the key and initial vector bytes
final byte[] passwordEncoded = password.getBytes(UTF_16LE);
final byte[] keyData = Arrays.copyOf(passwordEncoded, KEY_SIZE
/ Byte.SIZE);
final byte[] ivBytes = Arrays.copyOf(keyData, cipher.getBlockSize());
// --- init cipher
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyData, "AES"),
new IvParameterSpec(ivBytes));
// --- decode & decrypt authentication string
final byte[] authBytes = Base64.decode(authString);
final byte[] decryptedData = cipher.doFinal(authBytes);
// WARNING: may still decrypt to wrong string if
// authString or password are incorrect -
// BadPaddingException may *not* be thrown
return new String(decryptedData, UTF_16LE);
} catch (BadPaddingException | IllegalBlockSizeException e) {
// failure to authenticate
return null;
} catch (final GeneralSecurityException e) {
throw new IllegalStateException(
"Algorithms or unlimited crypto files not available", e);
}
}
}
The issues of your code:
Wrong character set chosen. If Encoding.Default is UTF-8 in C#, then the "password" and IV encodings must also be "UTF-8" in Java.
The IV is not derived from the key, but also a fixed value.
The ciphertext is actually doubly Base64 encoded. I guess somebody took "two is better than one" too literal.
Full code:
private static final String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5PADDING";
private static final int KEY_SIZE = 256;
private static String UTF_8 = "UTF-8";
public static void main(final String[] args) throws Exception {
System.out.println(decryptAuthorizationString(
"eGlhV2xNbmdqSFBkbEhkZDNpZ3gwQT09", "CB=Z8#P#0!N2/8$%3K-9C(5S9*FDH+0Z"));
}
private static String decryptAuthorizationString(final String authString,
final String password) throws UnsupportedEncodingException {
try {
// --- check if AES-256 is available
if (Cipher.getMaxAllowedKeyLength(AES_CBC_PKCS5PADDING) < KEY_SIZE) {
throw new IllegalStateException("Unlimited crypto files not present in this JRE");
}
// --- create cipher
final Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);
// --- create the key and initial vector bytes
final byte[] passwordEncoded = password.getBytes(UTF_8);
final byte[] ivBytes = "brasilshopsoft07".getBytes(UTF_8);
// --- init cipher
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(passwordEncoded, "AES"),
new IvParameterSpec(ivBytes));
// --- decode & decrypt authentication string
final byte[] authBytes = Base64.decode(authString);
final byte[] authBytes2 = Base64.decode(authBytes);
final byte[] decryptedData = cipher.doFinal(authBytes2);
// WARNING: may still decrypt to wrong string if
// authString or password are incorrect -
// BadPaddingException may *not* be thrown
return new String(decryptedData, UTF_8);
} catch (BadPaddingException | IllegalBlockSizeException e) {
// failure to authenticate
return null;
} catch (final GeneralSecurityException e) {
throw new IllegalStateException(
"Algorithms or unlimited crypto files not available", e);
}
}
Output:
1
Notes:
Using a fixed IV is insecure. The IV must be chosen randomly in order reach semantic security. It doesn't have to be secret, so it can be sent along with the ciphertext. A common way is to prepend it to the ciphertext and slice it off before decryption.
Lose the second Base64 encoding. It just takes away space, but doesn't provide any useful feature.
Always use a specific encoding. Encoding.Default is nice for testing, but it may change depending on the machine it's running on and you will lose compatibility between multiple clients/servers.
Authenticate ciphertexts! If you don't add a message authentication code or use an authenticated mode like GCM or EAX, then an attacker may manipulate the ciphertext and you will not be able to determine this. This can go so far as to completely recover the plaintext of specific encrypted messages (padding oracle attack).
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 7 years ago.
I have the following problem.
I have 2 functions in my code which are intended to encrypt / decrypt simple string.
SO:
I have to pass a string "someString" to the function:
public static String doEncryption(String input) {
try {
if (!RSAService.areKeysPresent()) {
RSAService.generateKey();
}
ObjectInputStream inputStream;
// Encrypt the string using the public key
inputStream = new ObjectInputStream(new FileInputStream(PUBLIC_KEY_FILE));
PublicKey publicKey = (PublicKey) inputStream.readObject();
byte[] cipherText = RSAService.encrypt(input, publicKey);
return cipherText.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "ERROR: Public key file is probably missing";
}
the function doEncryption("someString") returns "[B#61decc8c"
Now I have to embed this string in a url and the server side code should get it from there.
So far it is all good , but when I call the function
public static String doDecryption(String input) {
try {
if (!RSAService.areKeysPresent()) {
RSAService.generateKey();
}
ObjectInputStream inputStream;
// Decrypt the cipher text using the private key.
inputStream = new ObjectInputStream(new FileInputStream(PRIVATE_KEY_FILE));
PrivateKey privateKey = (PrivateKey) inputStream.readObject();
String out = decrypt(input.getBytes(), privateKey);
return out;
} catch (Exception e) {
e.printStackTrace();
}
return "ERROR: Private key file is probably missing or doesn't match the public key";
}
the doDecryption("[B#61decc8c") screams with the following exception:
javax.crypto.BadPaddingException: Data must start with zero
at sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:325)
at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:272)
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:356)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:382)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at rsaendecryptor.RSAService.decrypt(RSAService.java:132)
at rsaendecryptor.RSAService.doDecryption(RSAService.java:180)
at rsaendecryptor.RSAEnDecrypt.main(RSAEnDecrypt.java:20)
java.lang.NullPointerException
at java.lang.String.<init>(String.java:556)
at rsaendecryptor.RSAService.decrypt(RSAService.java:138)
at rsaendecryptor.RSAService.doDecryption(RSAService.java:180)
at rsaendecryptor.RSAEnDecrypt.main(RSAEnDecrypt.java:20)
Is there any way I can work around this? I have to pass string between the client and the server side because they can be even in different domains. Not to mention that the string will be actually generated from .Net logic and send to Java server side. Encryption to string works fine... What should I do to fix the decryption.
Here is the full class code:
public class RSAService {
/**
* String to hold name of the encryption algorithm.
*/
public static final String ALGORITHM = "RSA";
/**
* String to hold the name of the private key file.
*/
public static final String PRIVATE_KEY_FILE = "private.key";
/**
* String to hold name of the public key file.
*/
public static final String PUBLIC_KEY_FILE = "public.key";
/**
* Generate key which contains a pair of private and public key using 1024
* bytes. Store the set of keys in Prvate.key and Public.key files.
*
*/
public static void generateKey() {
try {
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
keyGen.initialize(1024);
final KeyPair key = keyGen.generateKeyPair();
File privateKeyFile = new File(PRIVATE_KEY_FILE);
File publicKeyFile = new File(PUBLIC_KEY_FILE);
// Create files to store public and private key
privateKeyFile.createNewFile();
if (publicKeyFile.getParentFile() != null) {
publicKeyFile.getParentFile().mkdirs();
}
publicKeyFile.createNewFile();
// Saving the Public key in a file
ObjectOutputStream publicKeyOS = new ObjectOutputStream(
new FileOutputStream(publicKeyFile));
publicKeyOS.writeObject(key.getPublic());
publicKeyOS.close();
// Saving the Private key in a file
ObjectOutputStream privateKeyOS = new ObjectOutputStream(
new FileOutputStream(privateKeyFile));
privateKeyOS.writeObject(key.getPrivate());
privateKeyOS.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* The method checks if the pair of public and private key has been
* generated.
*
* #return flag indicating if the pair of keys were generated.
*/
public static boolean areKeysPresent() {
File privateKey = new File(PRIVATE_KEY_FILE);
File publicKey = new File(PUBLIC_KEY_FILE);
if (privateKey.exists() && publicKey.exists()) {
return true;
}
return false;
}
/**
* Encrypt the plain text using public key.
*
* #param text : original plain text
* #param key :The public key
* #return Encrypted text
* #throws java.lang.Exception
*/
public static byte[] encrypt(String text, PublicKey key) {
byte[] cipherText = null;
try {
// get an RSA cipher object and print the provider
final Cipher cipher = Cipher.getInstance(ALGORITHM);
// encrypt the plain text using the public key
cipher.init(Cipher.ENCRYPT_MODE, key);
cipherText = cipher.doFinal(text.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
return cipherText;
}
/**
* Decrypt text using private key.
*
* #param text :encrypted text
* #param key :The private key
* #return plain text
* #throws java.lang.Exception
*/
public static String decrypt(byte[] text, PrivateKey key) {
byte[] dectyptedText = null;
try {
// get an RSA cipher object and print the provider
final Cipher cipher = Cipher.getInstance(ALGORITHM);
// decrypt the text using the private key
cipher.init(Cipher.DECRYPT_MODE, key);
dectyptedText = cipher.doFinal(text);
} catch (Exception ex) {
ex.printStackTrace();
}
return new String(dectyptedText);
}
public static String doEncryption(String input) {
try {
if (!RSAService.areKeysPresent()) {
RSAService.generateKey();
}
ObjectInputStream inputStream;
// Encrypt the string using the public key
inputStream = new ObjectInputStream(new FileInputStream(PUBLIC_KEY_FILE));
PublicKey publicKey = (PublicKey) inputStream.readObject();
byte[] cipherText = RSAService.encrypt(input, publicKey);
return cipherText.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "ERROR: Public key file is probably missing";
}
public static String doDecryption(String input) {
try {
if (!RSAService.areKeysPresent()) {
RSAService.generateKey();
}
ObjectInputStream inputStream;
// Decrypt the cipher text using the private key.
inputStream = new ObjectInputStream(new FileInputStream(PRIVATE_KEY_FILE));
PrivateKey privateKey = (PrivateKey) inputStream.readObject();
String out = decrypt(input.getBytes(), privateKey);
return out;
} catch (Exception e) {
e.printStackTrace();
}
return "ERROR: Private key file is probably missing or doesn't match the public key";
}
}
public static String doEncryption(String input)
Stop right there. String is not a container for binary data, and therefore shouldn't have been used to contain the ciphertext in the first place. It should have been passed around as a byte[].
NB when you get an exception, don't guess at what the condition was and return a string that says what it 'probably' was. It makes debugging a guessing came. Use the message that came with the exception.
Thanks to shikjohari and this article here I was able to fix my code!
in the doEncryption() method I modified the return as follows:
return (Base64.encode(cipherText)).toString();
and in doDecryption() method I modified the return as follows:
String out = decrypt(Base64.decode(input), privateKey);
return out;
You can get the full code from my first post and just edit the returns of the two methods as per this post. Hope this helps.