Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Exploring XML Encryption, Part 2

Implement an XML Encryption engine

Return to article


Listing 2. The XmlEncryption class
		
 /*
 	DW/BS
	20020521
	XmlEncryption.java
	Listing 2
	A wrapper class that can generate complete XML encrypted file.
	It uses all the other classes. 
	Users of the XML Encryption Engine will only need to interact with 
	this class.
 */

import java.io.*;
import java.util.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import org.apache.crimson.tree.XmlDocument;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import gokul.java.io.base64.*;

public class XmlEncryption {


	// Name of the algorithm which will be used to encrypt the data.
	private String algoName = null;
	
	// Name of the secret key which was previously agreed upon 
	// and saved with the given name.
	private String keyName = null; 
	
	// ID attribute of the main encryption type structure 
	private String encId = null;
	
	// It will be used to get a new Document objects.
	private DocumentBuilder docBuilder = null;
	
	// The Document object holding the XML file
	private Document clearDoc = null;
	
	//The encryption key
	private Key encKey = null;
	
	//The decryption key
	private SecretKey decKey = null;

	
	// The default constructor
	public XmlEncryption() {
		// Create DocumentBuilder object from DocumentBuilderFactory.
		try {
			docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		} 
		catch (ParserConfigurationException e) { 
			docBuilder = null; 
		}
	}
	
		
	public void setAlgoName(String name) {
		this.algoName = name;
	}// End setAlgoName() 
	
	
	public String getAlgoName() {
		return this.algoName;
	}// End getAlgoName() 
	
	
	public void setKeyName (String key) {
		this.keyName = key; 
	}// End setKeyName()
	
	
	public String getKeyName() {
		return this.keyName; 
	}// End getKeyName()
	
	
	public void setEncId (String id) {
		this.encId = id;
	}// End setEncId()
	
	
	public String getEncId() {
		return this.encId;
	}// End setEncId()
	
	
	public void setClearDoc(String fileString) {
		try {
			ByteArrayInputStream bais = new ByteArrayInputStream
				(fileString.getBytes());
			this.clearDoc = docBuilder.parse(bais);
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}// End setClearDoc()
	
	
	public Document getClearDoc() {
		return this.clearDoc;
	}// End getClearDoc()
	
	
	public void setEncKey(Key encKey) {
		this.encKey = encKey;
	}// End setEncKey()
	
	
	public Key getEncKey() {
		return this.encKey;
	}// End getEncKey()
	
	
	public void setDecKey(SecretKey decKey) {
		this.decKey = decKey;
	}// End setDecKey()
	
	
	public Key getDecKey() {
		return this.decKey;
	}// End getDecKey()
	
	
	// Generate a Complete XML Encrypted File.
	public String encryptCompleteXmlFile() {
		// Take an Object of EncryptedData Class. 
		// It represents an <EncryptedData> Element.
		EncryptedData encDataObj = this.getEncryptedDataDoc(this.encId, "DOCUMENT");
		// Get XML Structure for <EncryptionMehtod> element.
		Document encMethodDoc = this.getEncryptionMethodDoc(this.algoName);	
		// Get XML Structure for <KeyInfo> element.		
		Document encKeyInfoDoc = this.getKeyInfoDoc(this.keyName);
		// Read the given file data which will be encrypted.
		String plainData = this.getString((XmlDocument)this.clearDoc);
		// Use of JCA/JCE to get encrypted data.
		String cipherData = this.getEncryptedData(plainData);
		// Get XML Structure for <CipherData> element.				
		Document cipherDataDoc = this.getCipherDataDoc(cipherData);
		// Join these XML Structures.
		encDataObj.addChild(encMethodDoc);
		encDataObj.addChild(encKeyInfoDoc);
		encDataObj.addChild(cipherDataDoc);
		//return the resultant in a file
		return getString((XmlDocument)encDataObj.getEncData());
	}// End encryptCompleteXmlFile()

	
	// Generate element encrypted XML file.
	public String encryptElementOfXmlFile(String elementName) {
	
		// Take an Object of EncryptedData Class. 
		// It represents <EncryptedData> Element.
		EncryptedData encDataObj = this.getEncryptedDataDoc(this.encId, "ELEMENT");
		// Get XML Structure for <EncryptionMehtod> element.
		Document encMethodDoc = this.getEncryptionMethodDoc(this.algoName);	
		// Get XML Structure for <KeyInfo> element.		
		Document encKeyInfoDoc = this.getKeyInfoDoc(this.keyName);
		//The node object is transformend into a string.
		String plainData = this.getClearNode(elementName).toString().trim();
		// Method call to get encrypted and base 64 encoded data.
		String base64cipherData = this.getEncryptedData(plainData);
		// Get XML Structure for <CipherData> element.				
		Document cipherDataDoc = this.getCipherDataDoc(base64cipherData);
		// Join these XML Structures.
		encDataObj.addChild(encMethodDoc);
		encDataObj.addChild(encKeyInfoDoc);
		encDataObj.addChild(cipherDataDoc);
		// Return the encrypted content as a Document type object.
		Document encElementDoc = encDataObj.getEncData();	
		//Replace the specified element with its encrypted form
		boolean repstatus = false;
		repstatus = replaceElement(encElementDoc,elementName);
		return getString((XmlDocument)clearDoc);
	}// End encryptElementOfXmlFile()


	// Generate element content encrypted XML file.
	public String encryptElementContentOfXmlFile(String elementName) {
		// Take an Object of EncryptedData Class. 
		// It represents <EncryptedData> Element.
		EncryptedData encDataObj = this.getEncryptedDataDoc(this.encId, "CONTENT");
		// Get XML Structure for <EncryptionMehtod> element.
		Document encMethodDoc = this.getEncryptionMethodDoc(this.algoName);	
		// Get XML Structure for <KeyInfo> element.		
		Document encKeyInfoDoc = this.getKeyInfoDoc(this.keyName);
		//The node object is transformed into a string.
		String plainData = this.getElementContent(elementName).trim();
		// Method call to get encrypted and base 64 encoded data.
		String base64cipherData = this.getEncryptedData(plainData);
		// Get XML Structure for <CipherData> element.				
		Document cipherDataDoc = this.getCipherDataDoc(base64cipherData);
		// Join these XML Structures.
		encDataObj.addChild(encMethodDoc);
		encDataObj.addChild(encKeyInfoDoc);
		encDataObj.addChild(cipherDataDoc);//
		// Return the encrypted content as a Document type object.
		Document encElementDoc = encDataObj.getEncData();
		//Replace the specified element with its encrypted form
		boolean repstatus = false;
		repstatus = replaceContent(encElementDoc, elementName);
		return getString((XmlDocument)clearDoc);
	}// End encryptElementOfXmlFile()

	
	// This is where the actual JCA/JCE data encryption takes place.
	public String getEncryptedData(String data) {
		String clearData = new String(data);
		String cipherDataBase64	= null;
		try {
			//padding the last byte with " "
			int pad = clearData.length()%8;
			for(int i=0;i<(8-pad);i++) 
				clearData += " ";
			// Creating a Cipher Object.
			Cipher cipherObj = Cipher.getInstance(this.algoName+"/CBC/NoPadding");
			// Initializing the Cipher Object in encryption mode.
			cipherObj.init(Cipher.ENCRYPT_MODE,this.encKey);
			// Retreiving the IV
			byte [] iv = cipherObj.getIV();
			//Ciphering the clear text.
			byte[] cipherTemp = cipherObj.doFinal(clearData.getBytes());
			//Prefixing the IV to the cipher string to produce cipherData
			int ivlen = iv.length;
			int cTlen = cipherTemp.length;
			int cDlen = ivlen+cTlen	;
			byte [] cipherData = new byte[cDlen];
			// First 8 bytes are IV bytes
			for (int i=0;i<ivlen;i++) 
				cipherData[i] = (byte)iv[i];
			// Rest of the bytes are cipher bytes	
			for (int i=ivlen;i<cDlen;i++)
				cipherData[i] = (byte)cipherTemp[i-8];	
			//Base 64 encoding of cipherData
			cipherDataBase64 = getBase64Encoded(cipherData);
		}
		catch (Exception e) {
			System.out.println("Problem in getEncryptedData()");
			e.printStackTrace();
		}
		return cipherDataBase64;
	}// End getEncryptedData()
	
	//Decrypting the XML Encrypted file
	public String getDecryptedData(String encString) {
		String decString = "UNDER DEVELOPMENT";
		try	{
			//get the encrypted XML file string parsed into a Document object
			ByteArrayInputStream bais = new ByteArrayInputStream
				(encString.getBytes());
			Document encDoc = docBuilder.parse(bais);
			//Get a list of all the EncryptedData tags 
			NodeList nl = encDoc.getElementsByTagName("EncryptedData");
			//Load, decrypt and replace 
			//each EncryptedData tag in the Document object
			for(int i=0;i<nl.getLength();i++) {
				//Loading an element
				Node edata = nl.item(i);
				//Extracting the values of  Algorithm, KeyName, 
				//Type(of encryption) and CipherValue
				String algo = null;
				String keyname = null;
				String encType = null;
				String ciphervalue = null;
				//Setting the values 
				edata.normalize();
				//Setting the value of encType
				encType = edata.getAttributes().getNamedItem("Type")
					.getNodeValue();
				//Setting the values of the remaining parameters
				NodeList algoNL = edata.getChildNodes();
				for(int j=0;j<algoNL.getLength();j++) {
					//Setting the value of algo
					if(algoNL.item(j).getNodeName().equals
						("EncryptionMethod"))
						algo = algoNL.item(j).getAttributes().
							getNamedItem("Algorithm").getNodeValue();
					//Setting the value of keyname
					if(algoNL.item(j).getNodeName().equals("ds:KeyInfo")) {
						NodeList knNL = algoNL.item(j).getChildNodes();
						for(int k=0;k<knNL.getLength();k++)	{
							if(knNL.item(k).getNodeName().equals
								("KeyName"))
								keyname = (knNL.item(k).getFirstChild().
									getNodeValue());
						}
					}
					//Setting the value of ciphervalue
					if(algoNL.item(j).getNodeName().equals("CipherData")) {
						NodeList cvNL = algoNL.item(j).getChildNodes();
						for(int v=0;v<cvNL.getLength();v++)	{
							if(cvNL.item(v).getNodeName().equals
								("CipherValue"))
								ciphervalue = (cvNL.item(v)
									.getFirstChild().getNodeValue());
						}
					}
				}
				if (algo.equals
					("http://www.w3.org/2001/04/xmlenc#tripledes-cbc"))
					algo = "DESede";
				//Reading the key file and generating/setting decKey
				this.generateDecKey(keyname, algo);
				//Decrypt the cipher 
				String decbit = Decrypt(ciphervalue,this.decKey,algo).trim();
				//Replacement Logic
				//For replacing an entire XML file
				if(encType.equals(
			"http://www.isi.edu/in-notes/iana/assignments/media-types/text/xml"))
					decString = decbit;
				//Decrypted element replacement	
				else 
				if(encType.equals("http://www.w3.org/2001/04/xmlenc#Element")) {
					try	{
						byte [] a = decbit.getBytes();
						ByteArrayInputStream bais2 = 
							new ByteArrayInputStream(a);
						Document decdoc = docBuilder.parse(bais2); 
						Node decNode = 
							encDoc.importNode
								(decdoc.getFirstChild(), true);
						edata.getParentNode().replaceChild(decNode,edata);
					}
					catch(org.xml.sax.SAXParseException spe) {
						Text decText = encDoc.createTextNode(decbit);
						edata.getParentNode().replaceChild(decText,edata);
					}
					decString = (getString((XmlDocument)encDoc));
				}
				//Decrypted content replacement
				else if(encType.equals
					("http://www.w3.org/2001/04/xmlenc#Content")) {
					try {
						// Works if the content is a single child element.
						byte [] a = decbit.getBytes();
						ByteArrayInputStream bais2 = 
							new ByteArrayInputStream(a);
						Document decdoc = docBuilder.parse(bais2); 
						Node decNode = 
							encDoc.importNode
							(decdoc.getFirstChild(), true);
						edata.getParentNode().replaceChild(decNode,edata);
					}
					catch(org.xml.sax.SAXParseException spe) {
						//In case the content is plain text 
						//or a group of child elements
						Text decText = encDoc.createTextNode(decbit);
						edata.getParentNode().replaceChild(decText,edata);
					}
					decString = (getString((XmlDocument)encDoc));
				}
			}		
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		return decString;			
	}// End getDecryptedData()
	
		
	// This is where the actual JCA/JCE data decryption takes place.
	private String Decrypt(String encString, Key decKey, String algo) {
		// Decoding the Base 64 Encoded IV+cipher String into a byte array
		byte[] g = getBase64Decoded(encString);
		int glen = g.length;
		// Separating the IV from the byte array
		byte [] iv = new byte[8]; 
		for(int t=0;t<8;t++)
			iv[t] = g[t];
		// Separating the cipher from the byte array
		byte [] Enc = new byte[glen-8];
		for(int p=8;p<glen;p++)
			Enc[p-8] = g[p];
		// This will hold the decrypted String
		String decString = null;
		// Decrypting the cipher and setting decString:
		try {
			IvParameterSpec ivps = new IvParameterSpec(iv);
			AlgorithmParameters aparam = AlgorithmParameters.getInstance(algo);
			aparam.init(ivps);
		    Cipher cipherObj = Cipher.getInstance(algo+"/CBC/NoPadding");
			cipherObj.init(Cipher.DECRYPT_MODE, decKey, aparam);
			decString = new String(cipherObj.update(Enc));
		}
		catch(Exception e) {
			System.out.println("Problem in Decrypt()");
			e.printStackTrace();
		}
		return decString;
	}//End Decrypt()
	
	
	//Base 64 Encoding a byte array and returning it as a String
	private String getBase64Encoded(byte[] plainText) {
		String encoded = null;
		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			Base64OutputStream out= new Base64OutputStream(baos,true);
			out.write(plainText);
			out.close();
			String encText = new String(baos.toByteArray());
			baos.close();
			encoded = encText;
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		return encoded;
	}//End getBase64Encoded()
	
	//Base 64 Decoding a String and returning it as a byte array
	private byte[] getBase64Decoded(String codedText) {
		String ct = codedText;
		int lenEnc = ct.length();
		int lenDec = 0;
		// To calculate the max size of the decoded byte array	
		if (ct.charAt(lenEnc-1) == '=')
				if (ct.charAt(lenEnc-2) == '=') {
					lenDec = ((lenEnc-4)* 3/4);
					lenDec = ((lenDec - (lenDec/77))+1);
				}
				else {
					lenDec = ((lenEnc-4)* 3/4);
					lenDec = ((lenDec - (lenDec/77))+2);
				}
			else {
				lenDec = (lenEnc * 3/4);
				lenDec = (lenDec - (lenDec/77));
			}
		byte [] decoded = new byte[lenDec];
		try {
			
			ByteArrayInputStream bais = new ByteArrayInputStream(ct.getBytes());
			Base64InputStream in = new Base64InputStream(bais);
			in.read(decoded);
			in.close();
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		return decoded;
	}// End getBase64Decoded()
	
	// To generate the named Key Object and to set the decryption key
	private void generateDecKey(String keyname, String algo) {
		try {
			FileInputStream dkFIS = new FileInputStream(keyname);
			byte [] dk = new byte[dkFIS.available()];
			dkFIS.read(dk);
			dkFIS.close();
			// Generating/setting the decryption key.
			try {
				SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algo);
				DESedeKeySpec dkSpec = new DESedeKeySpec(dk);
				SecretKey sKey = keyFactory.generateSecret(dkSpec);
				this.setDecKey(sKey); 
			}
			catch(Exception e) {
				System.out.println("Unable to generate/set the decryption key.");
				e.printStackTrace();
			}
		}
		catch(Exception e) {
			System.out.println("Unable to set the decryption key.");
			e.printStackTrace();
		}
	}// End generateDecKey()

		
	//Replace the specified element with the encrypted data element
	private boolean replaceElement(Document edDoc, String elementName) {
		boolean isReplaced = false;
		try {
			Node clearNode = this.getClearNode(elementName);
			Node parentNode = clearNode.getParentNode();
			Node edNode = clearDoc.importNode(edDoc.getFirstChild(), true);
			parentNode.replaceChild(edNode,clearNode);
			isReplaced = true;
		}
		catch(Exception e) {
			isReplaced = false;
			System.out.println("\nReplacement Failed.");
			e.printStackTrace();
		}
		return isReplaced;
	}//End replaceElement()
	
	
	//Replace the specified element's content with the encrypted data element
	private boolean replaceContent(Document edDoc, String elementName) {
		boolean isReplaced = false;
		try {
			if (removeAllChildNodes(elementName)) {
				Node parentNode = this.getClearNode(elementName);
				//Importing the encryptedData Node
				Node edNode = clearDoc.importNode(edDoc.getFirstChild(), true);
				//Appending to an already empty parentNode
				parentNode.appendChild(edNode);
				isReplaced = true;		
			}
			else 
				System.out.println(
					"Child nodes not removed; replacement did not initiate.");
		}
		catch(Exception e) {
			isReplaced = false;
			System.out.println("\nContent replacement failed.");
			e.printStackTrace();
		}
		return isReplaced;
	}//End replaceContent()
	
	
	//Search the document tree and return the specified element as a Node Object.
	private Node getElement(Document doc, String elementName) {
 		NodeList nList = doc.getElementsByTagName(elementName);
		Node theNode = nList.item(0);
		return theNode;	
	}// End getElement();
	
	
	//Get a clear node by its element name
	private Node getClearNode(String elementName) {
		return getElement(this.clearDoc, elementName);
	}// End getClearNode()
		
	
	//Get a clear node's content by its element name
	public String getElementContent(String elementName) {
		String elementContent = new String();
		NodeList nl = this.getClearNode(elementName).getChildNodes();
		for(int i=0;i<nl.getLength();i++) 
			elementContent += (nl.item(i)).toString().trim();
		return elementContent;	
	}// End setElementContent()
	
	
	//Remove all the child nodes of clearNode
	private boolean removeAllChildNodes(String elementName) {
		boolean removed = false;
		Node clearNode = this.getClearNode(elementName);
		try	{
			while(clearNode.hasChildNodes()) {
				NodeList t = clearNode.getChildNodes();	
				// Removing a child Node 
				clearNode.removeChild(t.item(0));
			}//All children removed
			removed = true;
		}
		catch(Exception e) {
			removed = false;
			e.printStackTrace();
		}
		return removed;
	}//End removeAllChildNodes()
	
	
	// Get the new Document object for Document Builder
	private Document getNewDocument() {
		if (docBuilder != null)
			return docBuilder.newDocument();
		else
			return null;	
	}


	// Returns the EncryptedData object.
	public EncryptedData getEncryptedDataDoc(String Id, String encType) {
		EncryptedData ed = new EncryptedData(this.getNewDocument());
		ed.setId(Id);
		if (encType.equals("DOCUMENT"))
			ed.setType(AlgoNames.DOCUMENT);
		if (encType.equals("ELEMENT"))
			ed.setType(AlgoNames.ELEMENT);	
		if (encType.equals("CONTENT"))
			ed.setType(AlgoNames.CONTENT);	
		return ed;
	}// End getEncryptedDataDoc()
	
	
	// Returns the <EncryptionMehtod> structure.
	public Document getEncryptionMethodDoc (String algoName) {
		EncryptionMethod em = new EncryptionMethod(this.getNewDocument());
		if (algoName.equals("DESede"))
			em.setAlgorithm(AlgoNames.TRIPLE_DES);
		return em.getEncMethod();
	}// End getEncryptionMethodDoc()


	// Returns the <KeyInfo> structure.
	public Document getKeyInfoDoc (String keyName) {
		GenericKeyInfo ki = 
			new GenericKeyInfo(this.getNewDocument(),"ds", AlgoNames.XML_DSIG);
		ki.setKeyName(keyName);
		return ki.getKeyInfo();	
	}// End getKeyInfoDoc()


	// Returns the <CipherData> structure.
	public Document getCipherDataDoc (String data) {
		CipherData cd = new CipherData(this.getNewDocument());
		cd.setValue(data);
		return cd.getCipherData();
	}// getCipherDataDoc()
	
		
	//Convert an XmlDocument type object into a String
	public String getString(XmlDocument xmldoc) {	
		String DocumentAsString = null;
		try	{
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			xmldoc.write(bos);
			DocumentAsString = bos.toString();
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		return DocumentAsString;
	}//End getString()
	
	
	//Convert a Document type object into a String 
	//The output string does not contain initial encoding tags
	public String getString(Document doc) {	
		String DocumentAsString = null;
		try	{
			if (doc.getFirstChild().getNodeName().equals("#comment")) {
				DocumentAsString = "<!--"+doc.getFirstChild().getNodeValue()+
					"-->\n\n";
				DocumentAsString += doc.getDocumentElement().toString();
			}
			else
				DocumentAsString = doc.getDocumentElement().toString();
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		return DocumentAsString;
	}//End getString() 
	

}// End class demoXmlEncApp

Return to article