The following are resources and information that I hope will be of use to others.
On a recent project, I needed confidentiality and authenticity when communicating between a Windows client and a Linux server. I was constrained on the Windows side into using Microsoft's CNG / BCrypt API since the client's cryptography was implemented in a kernel-mode driver, which Microsoft supports via CNG. The Linux side obviously does not have CNG, so I used the popular Botan cryptography library. For my use-case, I used a pre-shared key implementation. I used AES-128/GCM(16) on both for encryption and to authenticate.
At the time of development, there was little in the way of documentation on how to accomplish this and have it interoperate between the two implementations. With a bit of trial and error (mostly error), I cobbled together a basic implementation from examples online for both the CNG and Botan implementations. With some generous support from the Botan mailing list, I was able to get it working on both sides.
In the hopes of saving future developers a significant amount of time, I have provided code snippets for both implementations, along with their listings/outputs.
Prompt> ./enctest Key: PASSWORDpasswordPASSWA== Initialization Vector: Buffer (size: 12): AAAAAAAAAAAAAAAA Plaintext (128 bytes): ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567809AB AES-128/GCM(16) ciphertext: xU8x3X8qDakf65Y9xcwxPqEK9yeiUx87RGIItr5gxR3fx1krHsQZS6YGYBq9mvwL4WnjeYe3nqFaZ2Y5n5hLnrKnMoCI2jL08I9qOnMIoTtJp/7ju5bCuwb0zzI+TlYyY6ta5KmlJNhGarrFmmn+mb74cNsbEA1sU6AjggftFkg= Auth Tag: cuHjwqAklGjyCAcm4mFleQ== Prompt>
Prompt>Project1.exe Auth Tag Length (min): 12 Auth Tag Length (max): 16 Block Length: 16 Key: Buffer (size: 16): PASSWORDpasswordPASSWA== Obtained algorithm name: AES Obtained chaining mode: ChainingModeGCM Initialization Vector: Buffer (size: 12): AAAAAAAAAAAAAAAA Nonce: Buffer (size: 12): AAAAAAAAAAAAAAAA Plaintext (128 bytes): ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567809AB AES-128/GCM(16) ciphertext: Buffer (size: 128): xU8x3X8qDakf65Y9xcwxPqEK9yeiUx87RGIItr5gxR3fx1krHsQZS6YGYBq9mvwL4WnjeYe3nqFaZ2Y5n5hLnrKnMoCI2jL08I9qOnMIoTtJp/7ju5bCuwb0zzI+TlYyY6ta5KmlJNhGarrFmmn+mb74cNsbEA1sU6AjggftFkg= Auth Tag: Buffer (size: 16): cuHjwqAklGjyCAcm4mFleQ== Prompt>
#include <botan/rng.h> #include <botan/auto_rng.h> #include <botan/cipher_mode.h> #include <botan/block_cipher.h> #include <botan/hex.h> #include <botan/base64.h> #include <botan/pipe.h> #include <iostream> #include <botan/key_filt.h> #include <botan/cipher_filter.h> #include <botan/b64_filt.h> #include <botan/mac.h> // Base 64 library: // http://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp #include "base64.h" using namespace std; int main() { // This needs to be a multiple of the block size (128 bits/16 bytes) for Windows CNG. Our example is 128 bytes in ASCII (16 * 8 = 128) std::string plaintext("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567809AB"); uint8_t keyStorage[65]; size_t consumedAmount; Botan::base64_decode(keyStorage, "PASSWORDpasswordPASSWORDpassword", 32, consumedAmount, true, false); Botan::secure_vector<uint8_t> key(keyStorage, keyStorage+16); cout << "Key: " << base64_encode(&key[0], 16) << endl; // To be clear, we're using an IV and a GCM nonce that are all // zeroes. This is only for testing. In a real application, // you MUST ensure the GCM nonce is never repeated. If you fail // to do so, your implementation will be inherently insecure. std::vector<uint8_t> iv = {0,0,0,0, 0,0,0,0, 0,0,0,0}; Botan::Keyed_Filter *gcm = Botan::get_cipher("AES-128/GCM(16)", key, Botan::ENCRYPTION); Botan::Pipe pipe(gcm); gcm->set_iv(iv); pipe.process_msg(plaintext.c_str()); Botan::secure_vector<uint8_t> output = pipe.read_all(0); std::cout << "Initialization Vector: Buffer (size: 12): " << base64_encode(&iv[0], 12) << std::endl; std::cout << "Plaintext (" << plaintext.length() << " bytes): " << plaintext << std::endl; std::cout << "AES-128/GCM(16) ciphertext: " << base64_encode(&output[0], 128) << std::endl; std::cout << "Auth Tag: " << base64_encode(&output[128], 16) << std::endl; }
#include <windows.h> #include <assert.h> #include <vector> #include <Bcrypt.h> #include <iostream> #include <string> #include <string.h> #include <wincrypt.h> #include <ntstatus.h> #pragma comment(lib, "bcrypt.lib") // Base 64 library: // http://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp #include "base64.h" void printB64(BYTE *buffer, size_t length) { std::string result = base64_encode(buffer, length); std::cout << "Buffer (size: " << length << "): " << result << std::endl; } int main(int argc, CHAR* argv[]) { NTSTATUS bcryptResult = 0; DWORD bytesDone = 0; // This gets us the AES algorithm: BCRYPT_ALG_HANDLE algHandle = 0; bcryptResult = BCryptOpenAlgorithmProvider(&algHandle, BCRYPT_AES_ALGORITHM, 0, 0); assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptOpenAlgorithmProvider"); // This sets up the GCM chaining mode: bcryptResult = BCryptSetProperty(algHandle, BCRYPT_CHAINING_MODE, (BYTE*) BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0); assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptSetProperty(BCRYPT_CHAINING_MODE)"); // This tells us the length of the authentication tag: BCRYPT_AUTH_TAG_LENGTHS_STRUCT authTagLengths; bcryptResult = BCryptGetProperty(algHandle, BCRYPT_AUTH_TAG_LENGTH, (BYTE*) &authTagLengths, sizeof(authTagLengths), &bytesDone, 0); assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptGetProperty(BCRYPT_AUTH_TAG_LENGTH)"); std::cout << "Auth Tag Length (min): " << authTagLengths.dwMinLength << std::endl; std::cout << "Auth Tag Length (max): " << authTagLengths.dwMaxLength << std::endl; // This tells us the length of the block: DWORD blockLength = 0; bcryptResult = BCryptGetProperty(algHandle, BCRYPT_BLOCK_LENGTH, (BYTE*) &blockLength, sizeof(blockLength), &bytesDone, 0); assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptGetProperty(BCRYPT_BLOCK_LENGTH)"); std::cout << "Block Length: " << blockLength << std::endl; BCRYPT_KEY_HANDLE keyHandle = 0; std::string decodedKeyString = base64_decode("PASSWORDpasswordPASSWORDpassword"); const std::vector<BYTE> key(decodedKeyString.data(), decodedKeyString.data() + blockLength); // This function takes the key buffer and directly uses it as a key. It does not manipulate // it in a way that would cause it not to match on the Botan side. bcryptResult = BCryptGenerateSymmetricKey(algHandle, &keyHandle, 0, 0, (PUCHAR) &key[0], key.size(), 0); assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptGenerateSymmetricKey"); std::cout << "Key: "; printB64((BYTE *) &key[0], key.size()); UCHAR namebuff[256]; ULONG tempSize = 256; // We ignore this because we know the buffer is big enough. const WCHAR *name = (const WCHAR *) namebuff; bcryptResult = BCryptGetProperty(algHandle, BCRYPT_ALGORITHM_NAME, namebuff, sizeof(namebuff), &tempSize, 0); std::cout << "Obtained algorithm name: "; std::wcout << name << std::endl; // Outputs: AES bcryptResult = BCryptGetProperty(algHandle, BCRYPT_CHAINING_MODE, namebuff, sizeof(namebuff), &tempSize, 0); std::cout << "Obtained chaining mode: "; std::wcout << name << std::endl; // Outputs: ChainingModeGCM // To be clear, we're using an IV and a GCM nonce that are all // zeroes. This is only for testing. In a real application, // you MUST ensure the GCM nonce is never repeated. If you fail // to do so, your implementation will be inherently insecure. // Create an IV that is the same size as Botan's: const size_t AES_IV_SIZE = 12; const std::vector<BYTE> origIV = {0,0,0,0,0,0,0,0,0,0,0,0}; std::cout << "Initialization Vector: "; printB64((BYTE *) &origIV[0], origIV.size()); // This must always be 96 bits (12 bytes): const size_t GCM_NONCE_SIZE = 12; const std::vector<BYTE> origNonce = {0,0,0,0,0,0,0,0,0,0,0,0}; std::cout << "Nonce: "; printB64((BYTE *) &origNonce[0], origNonce.size()); // This needs to be a multiple of the block size (128 bits/16 bytes) for Windows CNG. Our example is 128 bytes in ASCII (16 * 8 = 128) std::string plaintext("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567809AB"); const std::vector<BYTE> plaintextVector(plaintext.data(), plaintext.data() + plaintext.length()); // We are going to do in-place encryption: std::vector<BYTE> encrypted = plaintextVector; std::vector<BYTE> authTag(authTagLengths.dwMaxLength); // This sets up our nonce and GCM authentication tag parameters: BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo; BCRYPT_INIT_AUTH_MODE_INFO(authInfo); authInfo.pbNonce = (PUCHAR) &origNonce[0]; // A nonce is required for GCM authInfo.cbNonce = origNonce.size(); // The size of the nonce is provided here authInfo.pbTag = &authTag[0]; // The buffer that will gain the authentication tag authInfo.cbTag = authTag.size(); // The size of the authentication tag std::cout << "Plaintext (" << plaintext.length() << " bytes): " << plaintext << std::endl; bcryptResult = BCryptEncrypt ( keyHandle, &encrypted[0], encrypted.size(), // Plaintext and its associated size &authInfo, // Must be a BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO for GCM (PUCHAR) &origIV[0], 12, // No initialization vector provided. We'd want to change this. &encrypted[0], encrypted.size(), // The buffer in which to store the encrypted output and its size &bytesDone, 0 ); std::cout << "AES-128/GCM(16) ciphertext: "; printB64((BYTE *) &encrypted[0], encrypted.size()); std::cout << "Auth Tag: "; printB64((BYTE *) &authTag[0], authTag.size()); assert(BCRYPT_SUCCESS(bcryptResult) || !"BCryptEncrypt"); assert(bytesDone == encrypted.size()); // Cleanup BCryptDestroyKey(keyHandle); BCryptCloseAlgorithmProvider(algHandle, 0); return 0; }
I created a system, InstructAssist, which is a course management system that I customize for my courses. It features in-class tools (selecting students randomly for questions/activities) along with customized tools for classes, including a "peer review" system for graduate courses and a gameified "ScoreKeeper" system for undergraduate network security courses.
In preparing my teaching materials, in particular for my undergraduate CS 4404 course (Tools and Techniques in Network Security), I have explored an interesting combination of software and configurations that are likely to be atypical. While boundary cases, I'm hoping these materials can help others.
At the time I was planning my course, there was a relative dearth of information on how to configure PFSense and Openswan IPSec daemons to interoperate. I was eventually able to get them to work, with some caveats, and I saw other postings asking about this.
Below are the configuration files that I used in Openswan and PFSense. You can certainly adjust the settings to meet your needs, but I thought a working example would help. In particular, I used relatively weaker cryptography since I was using a VPN for isolation and wanted performance, not confidentiality.
In the following, my pfSense box is at 130.215.a.b and that my Ubuntu Openswan box is at 130.215.x.y. The Ubuntu box has its own subnet (10.44.2.64/26) and the Openswan box has two subnets, a direct one (10.44.2.128/25) and one it routes to through another router (10.44.2.0/26).
Openswan
/etc/ipsec.conf:
version 2.0 config setup nat_traversal=no virtual_private=%v4:172.16.0.0/12 oe=off protostack=netkey conn CS4404DirectNetwork2 authby=secret pfs=no auto=add ike=aes128-sha1;modp1024 phase2=esp phase2alg=aes128-sha1;modp1024 keyingtries=3 rekey=no ikelifetime=8h keylife=1h type=tunnel aggrmode=no left=130.215.x.y leftsubnet=10.44.2.64/26 leftprotoport=0/%any right=%any rightprotoport=0/%any rightsubnet=10.44.2.128/25 conn CS4404ForwardNetwork2 authby=secret pfs=no auto=add ike=aes128-sha1;modp1024 phase2=esp phase2alg=aes128-sha1;modp1024 keyingtries=3 rekey=no ikelifetime=8h keylife=1h type=tunnel aggrmode=no left=130.215.x.y leftsubnet=10.44.2.64/26 leftprotoport=0/%any right=%any rightprotoport=0/%any rightsubnet=10.44.2.0/26
/etc/ipsec.secrets:
130.215.x.y %any: PSK "SecurityPassphraseGoesHere"
pfSense
You can back up pfSense to a config file and restore from it to set all the details without having to use the GUI (which is harder to explain). This is the contents of the file from the "Diagnostics" -> "Backup/Restore" option and choosing the "IPSEC" backup area (excluding RRD data).