Sunday, June 22, 2014

Apex Data Loader in Linux

Salesforce Apex Data Loader is only officially supported on the Windows platform but the source code is available as an open source project for anyone to build it for another platform such as Linux or Mac. The data loader is written in Java using the Spring Framework so it can be easily rebuilt for any platform that supports the JDK and corresponding Eclipse SWT for GUI. If you don't care much for the GUI to do the mapping and simply want to use it from the command-line, then the good news is that the same JAR file from the Windows installation can be used to run the process in another platform without having to rebuild the code.

What's missing however are the Linux version of the utility scripts (in the bin\ directory in windows installation) to do the password encryption and process execution. I recently had to execute the data loader jobs from Linux and ended up writing the missing scripts so it is easy to mimic the Windows command line process (as explained in Chapter 5 in the guide).

The code is on github and includes the default configuration options and a sample extract process. The directories mirror the Windows version and the following steps walk you through the process:
  1. From your Linux (or Mac) terminal, execute the following command to clone the project from github
     $ git clone https://github.com/sthiyaga/dataloader.git 
  2. Copy the dataloader-30.0.0-uber.jar from the Windows installation to dataloader/ directory (from above)
  3. Generate the private key to encrypt the password 
    $ bin/encrypt.sh -g <some-random-seed-text>
  4. Copy the output from Step 3 above to the file conf/private.key (replacing the text in there)
  5. Encrypt the salesforce password (+security token, if required) using the generated private key 
    $ bin/encrypt.sh -e "password+securitytoken" conf/private.key 
  6. Copy the output from Step 4 above to the conf/config.properties file for the sfdc.password token value
  7. Update the conf/config.properties file with sfdc.username and sfdc.endpoint token values
  8. Optionally, adjust any other default parameters in the conf/config.properties file
  9. Run the sample account extract process 
    $ bin/process.sh csvAccountExtractProcess
That's it, you should have the sample extract in the data/ directory.

Enjoy and feel free to use the code!

-Senthil

Friday, June 6, 2014

Java Encryption, Apex Decryption

Ever had a need to decrypt something in Apex that was encrypted in an external app? Well, I just ran into a need for it where an external Java web app generates a token that is encrypted with AES and when it is sent to Salesforce.com, the token had to be decrypted to do a bunch of stuff. There are a couple of things to note if you have a similar need -- Salesforce supports encryption/decryption using AES algorithm (128 bits, 192 bits and 256 bits) but is particular about the encryption mode and padding. The mode has to be cipher block chaining (CBC) and the padding has to be PKCS5. These are different from what Java supports by default, so it is important to pass the correct settings to initialize the cipher in the encrypting application. Secondly, the initialization vector (IV) used during AES encryption can be provided separately or as part of the encrypted text and the appropriate method in the Crypto class in Apex should be used for decrypting. Refer to the docs for the Crypto class for all the gory details - http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_restful_crypto.htm

Ok, now here is a sample class in Java for generating the AES 128-bit key that will be used by both the web application to encrypt and the Apex code to decrypt the token:

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.xml.bind.DatatypeConverter;

public class AESKeyGen {
public static String generatePrivateKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey secretKey = keyGen.generateKey();
return DatatypeConverter.printBase64Binary(secretKey.getEncoded()); 
}
public static void main(String[] args) throws Exception {
String privateKey = generatePrivateKey();
System.out.println("Private Key = " + privateKey);
}
}

When run, this will produce a Base64 encoded AES private key that is 128-bits long. Needless to say, this should be kept private from the prying eyes.  

Private Key = useALumM/MAmHU1+hgsnPg==

Next is the Java class that actually encrypts the given text using this private key. In this case I'm including the IV data as part of the encrypted text by prefixing it before encoding to Base64 format.

import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class AESEncrypt {
public static String encodeAES(String privateKey, String data) throws Exception {
final int AES_KEYLENGTH = 128;
byte[] iv = new byte[AES_KEYLENGTH / 8];
SecureRandom prng = new SecureRandom();
prng.nextBytes(iv);
Cipher aesCipherForEncryption = Cipher.getInstance("AES/CBC/PKCS5PADDING");
aesCipherForEncryption.init(Cipher.ENCRYPT_MODEnew SecretKeySpec(DatatypeConverter.parseBase64Binary(privateKey), "AES"), new IvParameterSpec(iv));
byte[] byteDataToEncrypt = data.getBytes();
byte[] byteCipherText = aesCipherForEncryption.doFinal(byteDataToEncrypt);

byte[] ivPlusCipher = new byte[iv.length + byteCipherText.length];
System.arraycopy(iv, 0, ivPlusCipher, 0, iv.length);
System.arraycopy(byteCipherText, 0, ivPlusCipher, iv.length, byteCipherText.length);
return DatatypeConverter.printBase64Binary(ivPlusCipher);
}
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("Usage: java AESEncrypt <private-key> <text>");
System.exit(-1);
}

System.out.println("Cipher Text = " + encodeAES(args[0], args[1]));
}
}

Here is a sample execution of the encryption class with the above private key: 

> java AESEncrypt "useALumM/MAmHU1+hgsnPg==" "Hello, (Encrypted) World!"

The output should be something similar to what you see below (it changes with each run as the IV is generated randomly each time) and again, the output includes the IV + Encrypted text in Base64 encoded format. 

Cipher Text = 86z+0A1LKPluwKEmeczUqMYMCLo6BlEOeHwx6zZh/bfsrXR8oRg2Z+csM9UHL59J

Now that we have our private key and encrypted text, the following lines of code will decrypt it in Apex (run through the Developer Console in this case) -- note the use of decryptWithManagedIV method since the encrypted text includes the IV in our case: 

Blob key = EncodingUtil.base64Decode('useALumM/MAmHU1+hgsnPg==');
Blob encData = EncodingUtil.base64Decode('86z+0A1LKPluwKEmeczUqMYMCLo6BlEOeHwx6zZh/bfsrXR8oRg2Z+csM9UHL59J');

Blob decryptedData = Crypto.decryptWithManagedIV('AES128', key, encData);

System.Debug( decryptedData.toString() );

Here is the output (from the debug log): 

18:16:50:025 USER_DEBUG [6]|DEBUG|Hello, (Encrypted) World!

That's it, feel free to reuse the code in your projects as needed. Happy Friday!

-Senthil