Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
joel_langoyan
Participant
OVERVIEW

We had requirement to sign and encrypt all API request to our client's partner bank and then decrypt the response. The bank called this encryption scheme as PKI encryption which upon research with the sample payload lead to the W3C Recommendation.

Here I will discussed how the encryption and decryption process can be achieved. Below is a sample of what an encrypted payload can be:
<xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<xenc:EncryptedKey>
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
<xenc:CipherData>
<xenc:CipherValue>b44GKIzOikbz.....</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedKey>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>CO+A40a3vGzo47.....</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>

For our scenario, the keys are encrypted using RSA v1.5 algorithm with the actual request encrypted with TripleDES. You would need the Apache Santuario library to this process.

 

PAYLOAD SIGNATURE

As mentioned, our requirement includes signing the payloads. Thankfully, I was able to skipped on this with Carlos blog SAP PI/PO XML X509 signature by certificate.

 

ENCRYPTING THE REQUEST

For this process, involves the following steps:

  1. Load the target servers certificate from TrustedCA or from where you have it stored in NWA.

  2. Get the Public key of the certificate which will be used for the encryption

  3. Generate a key will be used by target server for decryption.

  4. Encrypt the generated key.

  5. Encrypt the signed request payload using the generated key.


import java.io.IOException;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.xml.security.encryption.EncryptedData;
import org.apache.xml.security.encryption.EncryptedKey;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.encryption.XMLEncryptionException;
import org.apache.xml.security.keys.KeyInfo;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import com.sap.aii.mapping.api.AbstractTrace;
import com.sap.aii.mapping.api.AbstractTransformation;
import com.sap.aii.mapping.api.StreamTransformationException;
import com.sap.aii.mapping.api.TransformationInput;
import com.sap.aii.mapping.api.TransformationOutput;
import com.sap.aii.security.lib.KeyStoreManager;
import com.sap.security.api.ssf.ISsfProfile;

public class JM_XMLEncryption extends AbstractTransformation {
static AbstractTrace log = null;
@Override
public void transform(TransformationInput arg0, TransformationOutput arg1) throws StreamTransformationException {
log = this.getTrace();
String encKeyView = arg0.getInputParameters().getString("encKeyView");
String encKeyEntry = arg0.getInputParameters().getString("encKeyEntry");

try {
org.apache.xml.security.Init.init();
//load input payload as Document
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
Document document = dbf.newDocumentBuilder().parse(arg0.getInputPayload().getInputStream());

//load the certificate for Key store
ISsfProfile encryptProfile = getSsfProfileKeyStore(encKeyView,encKeyEntry);
X509Certificate certificate = encryptProfile.getCertificate();

//generate secret key
KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");
keyGenerator.init(168);
SecretKey skey = keyGenerator.generateKey();

//encrypt the secret key
PublicKey pubkey = certificate.getPublicKey();
XMLCipher keyCipher = XMLCipher.getInstance(XMLCipher.RSA_v1dot5);
keyCipher.init(XMLCipher.WRAP_MODE, pubkey);
EncryptedKey encKey = keyCipher.encryptKey(document, skey);

//initialize cipher for encryption of document
XMLCipher xmlCipher = XMLCipher.getInstance(XMLCipher.TRIPLEDES);
xmlCipher.init(XMLCipher.ENCRYPT_MODE, skey);

//add key info to encrypted Data
EncryptedData encData = xmlCipher.getEncryptedData();
KeyInfo keyInfo = new KeyInfo(document);
keyInfo.add(encKey);
encData.setKeyInfo(keyInfo);

//encrypt the document
xmlCipher.doFinal(document, document.getDocumentElement(), false);

//Output the resulting document.
OutputStream os = arg1.getOutputPayload().getOutputStream();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
trans.transform(new DOMSource(document), new StreamResult(os));

} catch (SAXException | IOException | ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (XMLEncryptionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (TransformerConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (TransformerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

private static ISsfProfile getSsfProfileKeyStore(String keyStoreAlias, String keyStoreEntry) throws StreamTransformationException {
KeyStoreManager managerPriviliged = null;
try {
managerPriviliged = com.sap.aii.af.service.resource.SAPSecurityResources.getInstance().getKeyStoreManager(
com.sap.aii.security.lib.PermissionMode.SYSTEM_LEVEL);
} catch (KeyStoreException e) {
throw new StreamTransformationException("SAPSecurityResources", e);
}
KeyStore keyStore;
try {
keyStore = managerPriviliged.getKeyStore(keyStoreAlias);
} catch (KeyStoreException e) {
throw new StreamTransformationException("managerPriviliged.getKeyStore " + keyStoreAlias, e);
}
ISsfProfile profile = null;
try {
profile = managerPriviliged.getISsfProfile(keyStore, keyStoreEntry, null);
} catch (KeyStoreException e) {
throw new StreamTransformationException("Failed to load SsfProfileKeyStore " + keyStoreAlias + " " + keyStoreEntry, e);
}
return profile;
}
}

 

DECRYPTING THE RESPONSE

For this process, involves the following steps:

  1. Get values from respective encrypted key and data contained in the CipherValue elements.

  2. Load your private key from NWA

  3. Decrypt the encrypted key using your private key.

  4. Generate the secret key using related algorithm.

  5. Decrypt the encrypted data with the secret key


import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.sap.aii.af.service.resource.SAPSecurityResources;
import com.sap.aii.mapping.api.AbstractTrace;
import com.sap.aii.mapping.api.AbstractTransformation;
import com.sap.aii.mapping.api.StreamTransformationException;
import com.sap.aii.mapping.api.TransformationInput;
import com.sap.aii.mapping.api.TransformationOutput;
import com.sap.aii.security.lib.KeyStoreManager;
import com.sap.security.api.ssf.ISsfProfile;

public class JM_XMLDecryption extends AbstractTransformation {

static AbstractTrace log = null;

@Override
public void transform(TransformationInput arg0, TransformationOutput arg1) throws StreamTransformationException {
log = this.getTrace();
String keyView = arg0.getInputParameters().getString("keyView");
String keyEntry = arg0.getInputParameters().getString("keyEntry");
String password = arg0.getInputParameters().getString("password");
try {
org.apache.xml.security.Init.init();
//build document from input payload
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
Document document = dbf.newDocumentBuilder().parse(arg0.getInputPayload().getInputStream());

//get the encrypted key and data in the CipherValue elements
NodeList cipherValueNodes = document.getElementsByTagName("xenc:CipherValue");
Element encKey = null;
Element encData = null;
for(int i = 0; i < cipherValueNodes.getLength(); i++) {
if (i == 0) {
encKey = (Element)cipherValueNodes.item(i);
}
else {
encData = (Element)cipherValueNodes.item(i);
}
}

//decrypt the encrypted key
PrivateKey privKey = getCertProfile(keyView,keyEntry,password).getPrivateKey();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privKey);
byte[] encryptedKeyBytes = Base64.getDecoder().decode(encKey.getTextContent());
byte[] decryptedKey = cipher.doFinal(encryptedKeyBytes);

//generate key from decrypted key
DESedeKeySpec desEdeKeySpec = new DESedeKeySpec(decryptedKey);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey key = keyFactory.generateSecret(desEdeKeySpec);

//decrypt encrypted data
int ivLen = 8;
byte[] ivBytes = new byte[ivLen];
byte[] encryptedDataBytes = Base64.getDecoder().decode(encData.getTextContent());
System.arraycopy(encryptedDataBytes, 0, ivBytes, 0, ivLen);
IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);
Cipher cipherData = Cipher.getInstance("DESede/CBC/NoPadding");
cipherData.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
String decryptedData = new String(cipherData.doFinal(encryptedDataBytes, ivLen, encryptedDataBytes.length - ivLen));
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = docFactory.newDocumentBuilder();
InputSource is = new InputSource(new StringReader(decryptedData.substring(0, decryptedData.length()-3)));
Document doc = builder.parse(is);

OutputStream os = arg1.getOutputPayload().getOutputStream();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
trans.transform(new DOMSource(doc), new StreamResult(os));
}
catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TransformerConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TransformerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeySpecException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

private static ISsfProfile getCertProfile(String alias, String entry,String pwd) throws StreamTransformationException {
KeyStoreManager managerPriviliged;
try {
managerPriviliged = SAPSecurityResources.getInstance().getKeyStoreManager(com.sap.aii.security.lib.PermissionMode.SYSTEM_LEVEL);
} catch (KeyStoreException e) {
throw new StreamTransformationException("SAPSecurityResources", e);
}
KeyStore keyStore;
try {
keyStore = managerPriviliged.getKeyStore(alias);
} catch (KeyStoreException e) {
throw new StreamTransformationException("managerPriviliged.getKeyStore " + alias, e);
}
ISsfProfile profile = null;
try {
profile = managerPriviliged.getISsfProfile(keyStore, entry, null);
} catch (KeyStoreException e) {
throw new StreamTransformationException(
"Failed to load SsfProfileKeyStore " + alias + " " + entry, e);
}
return profile;
}

}

I would like to highlight specific section of the code as it might not be applicable to your scenario.

Depending on the algorithm, you may have to search for the equivalent Spec class of the algorithm in your scenario. In my case, it is TripleDES hence the DESedeKeySpec.
DESedeKeySpec desEdeKeySpec = new DESedeKeySpec(decryptedKey);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");

For the ivLen, the value also depends of the encryption algorithm applied. The size for TripleDES is 24 meaning DES applied three times hence the value is eight.
int ivLen = 8;

For whatever reason, the response from the server has extract three characters at the end of the entire payload hence the substring being applied.
InputSource is = new InputSource(new StringReader(decryptedData.substring(0, decryptedData.length()-3)));

 

 

REFERENCES

https://blogs.sap.com/2020/01/09/sap-pi-po-xml-x509-signature-by-certificate/

https://blogs.sap.com/2018/12/24/how-to-encryptdecrypt-xml-payload-with-aes256-cbc-and-rsa-algorithm...

https://answers.sap.com/questions/183771/keystore-access-from-java-mapping.html

https://apache.googlesource.com/santuario-java/+/9155bb454492745edfecc681b0708fe34dc3d511/samples/or...

https://stackoverflow.com/questions/57261251/examples-or-tutorials-about-santuario-java
2 Comments
Labels in this area