Quellcode durchsuchen

Merge branch 'us28-encryption-cli' into 'develop'

US28: File encryption on CLI

See merge request tobias.wach/ccats!46
Serdyukov, Denys vor 4 Jahren
Ursprung
Commit
8fdc418298

+ 6 - 0
cli/include/cmdman.h

@@ -111,6 +111,12 @@ private:
 	CmdRet cmdList(vector<string> args);
 	const string descHead = "request the first four bytes of a file from the server";
 	CmdRet cmdHead(vector<string> args);
+	/* TODO cmdKeyfile */
+	/* TODO cmdClosekey */
+	const string descKeyfile = "set keyfile to use";
+	CmdRet cmdKeyfile(vector<string> args);
+	const string descClosekey = "stop using the previously selected keyfile";
+	CmdRet cmdClosekey(vector<string> args);
 
 	const string descLogin = "login to the server";
 	CmdRet cmdLogin(vector<string> args);

+ 49 - 5
cli/include/fileman.h

@@ -2,11 +2,12 @@
 #define FILEMAN_H
 
 #include <fstream>
+#include <openssl/conf.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
 #include <string>
 #include <vector>
 
-#define BLOCKSIZE 8
-
 /**
  * @class FileMan
  *
@@ -25,11 +26,11 @@ private:
 	 * Boolean replacement for filestreams being open for list
 	 *
 	 */
-	std::ifstream putfile;
-	std::ofstream getfile;
+	std::vector<std::vector<char>> putdata;
+	std::fstream getfile;
 	std::vector<std::string> listdata;
 	std::string getpath, getname, putpath, putname;
-	const unsigned int max_read_len = 8;
+	const unsigned int max_read_len = 256;
 	int putsize;
 	int putchunks;
 	int putchunksRemaining;
@@ -38,7 +39,31 @@ private:
 	int listchunks;
 	int listchunksRemaining;
 
+	bool isputting;
 	bool islisting;
+	bool keyenabled;
+	bool cryptoreadye;
+	bool cryptoreadyd;
+	bool pendingerr;
+
+	unsigned char iv[12];  // 96bits
+	unsigned char tag[16]; // 128bits
+	unsigned char key[32]; // 256bits
+	const unsigned int cipherblocklen = 128;
+
+	EVP_CIPHER_CTX *cryptctxe;
+	EVP_CIPHER_CTX *cryptctxd;
+	std::string opensslerr;
+
+	void setOpensslError();
+	bool initCryptoE();
+	bool initCryptoD();
+	void deinitCryptoE();
+	void deinitCryptoD();
+
+	std::vector<std::vector<char>> chunkify(char *data, unsigned int size);
+	void writeEnc(const std::vector<char> data);
+	const char signature[4] = {'C', 'C', 'A', 'T'};
 
 public:
 	/**
@@ -55,6 +80,7 @@ public:
 	bool isGetting();
 	bool isPutting();
 	bool isListing();
+	bool isEncrypted();
 
 	/**
 	 * Check for and prepare state and streams for reading/writing
@@ -65,6 +91,14 @@ public:
 	bool openGet(const std::string &path);
 	bool openList();
 
+	/**
+	 * Open file and read a hex string from it as key and initialize the IV
+	 * This will fail is a put or a get is in progress
+	 *
+	 * Return true if successful, false otherwise. After this put and get will en/decrypt data
+	 */
+	bool openKey(const std::string &path);
+
 	/**
 	 * Close the respective filestream
 	 */
@@ -72,6 +106,14 @@ public:
 	void closeGet();
 	void closeList();
 
+	/**
+	 * Reset internal key state and disable en/decryption of data
+	 * This will fail if a put or get is in progress
+	 *
+	 * Return true if key was reset, false otherwise. After this put and get will use unencrypted data
+	 */
+	bool closeKey();
+
 	/**
 	 * Query the names of the file currently being put or get
 	 */
@@ -130,6 +172,8 @@ public:
 	 * Returns the filename of the passed (relative) path of a file
 	 */
 	std::string pathToFilename(std::string path);
+
+	std::string getOpensslError();
 };
 
 #endif

+ 2 - 0
cli/include/userioman.h

@@ -49,6 +49,8 @@ private:
 	void printListdata(Json::Value root);
 	void printHead(Json::Value root);
 	void printDeleteme(Json::Value root);
+	void printKeyfile(Json::Value root);
+	void printClosekey(Json::Value root);
 
 	/**
 	 * Mutex for synchronized message output

+ 49 - 0
cli/src/cmdman.cpp

@@ -34,6 +34,8 @@ CmdMan::CmdMan(FileMan &fm, void (*dpf)(string)) : fileman(fm) {
 	execmap["listdata"] = &CmdMan::cmdListdata;
 	execmap["head"] = &CmdMan::cmdHead;
 	execmap["deleteme"] = &CmdMan::cmdDeleteme;
+	execmap["keyfile"] = &CmdMan::cmdKeyfile;
+	execmap["closekey"] = &CmdMan::cmdClosekey;
 
 	/* initialize description map */
 	helpmap["help"] = descHelp;
@@ -46,6 +48,8 @@ CmdMan::CmdMan(FileMan &fm, void (*dpf)(string)) : fileman(fm) {
 	helpmap["login"] = descLogin;
 	helpmap["signup"] = descSignup;
 	helpmap["deleteme"] = descDeleteme;
+	helpmap["keyfile"] = descKeyfile;
+	helpmap["closekey"] = descClosekey;
 
 	/* initialize handle command map */
 	handlemap["status"] = &CmdMan::handleStatus;
@@ -302,6 +306,51 @@ CmdMan::CmdRet CmdMan::cmdDeleteme(vector<string> args) {
 	return retval;
 }
 
+CmdMan::CmdRet CmdMan::cmdKeyfile(vector<string> args) {
+	CmdRet retval;
+	Json::Value root;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+	root["command"] = "keyfile";
+	if (args.size() < 1) {
+		retval.type = error;
+		root["accept"] = false;
+		root["error"] = "not enough arguments, at least 1 argument required";
+	} else {
+		DEBUGPRINT(string(__PRETTY_FUNCTION__) + " haveargs");
+		if (!fileman.openKey(args[0])) {
+			DEBUGPRINT(string(__PRETTY_FUNCTION__) + " openkey fail");
+			root["accept"] = false;
+			root["file"] = args[0];
+			root["error"] = string("couldnt open keyfile, openssl reports: ") + fileman.getOpensslError();
+			retval.type = error;
+		} else {
+			DEBUGPRINT(string(__PRETTY_FUNCTION__) + " openkey good");
+			root["accept"] = true;
+			root["file"] = args[0];
+			retval.type = print;
+		}
+	}
+	retval.msg = root;
+	return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdClosekey(vector<string> args) {
+	CmdRet retval;
+	Json::Value root;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+	root["command"] = "closekey";
+	if (!fileman.closeKey()) {
+		root["accept"] = false;
+		root["error"] = "couldnt close keyfile. ensure no put or get is running";
+		retval.type = error;
+	} else {
+		root["accept"] = true;
+		retval.type = print;
+	}
+	retval.msg = root;
+	return retval;
+}
+
 /* login and signup commands */
 CmdMan::CmdRet CmdMan::cmdLogin(vector<string> args) {
 	CmdRet retval;

+ 300 - 34
cli/src/fileman.cpp

@@ -1,41 +1,143 @@
 #include "../include/fileman.h"
 #include "../include/base64.h"
 
-FileMan::FileMan() { islisting = false; }
+#include <cstdio>
+#include <cstring>
+#include <iostream>
+
+using std::string;
+using std::vector;
+
+FileMan::FileMan() {
+	isputting = false;
+	islisting = false;
+	keyenabled = false;
+	cryptoreadye = false;
+	cryptoreadyd = false;
+	ERR_load_crypto_strings();
+}
 
 FileMan::~FileMan() {
 	cancelGet();
 	cancelPut();
 	cancelList();
+	if (keyenabled)
+		closeKey();
+	ERR_free_strings();
 }
 
 bool FileMan::isGetting() { return getfile.is_open(); }
 
-bool FileMan::isPutting() { return putfile.is_open(); }
+bool FileMan::isPutting() { return isputting; }
 
 bool FileMan::isListing() { return islisting; }
 
-bool FileMan::openPut(const std::string &path) {
+bool FileMan::isEncrypted() { return keyenabled; }
+
+vector<vector<char>> FileMan::chunkify(char *data, unsigned int size) {
+	vector<vector<char>> ret;
+	vector<char> chunk;
+	unsigned int i;
+	for (i = 0; (i + max_read_len) < size; i += max_read_len) {
+		chunk = vector<char>(data + i, data + i + max_read_len);
+		ret.push_back(chunk);
+	}
+	chunk = vector<char>(data + i, data + size);
+	ret.push_back(chunk);
+	return ret;
+}
+
+bool FileMan::openPut(const string &path) {
+	std::ifstream putfile;
+	std::fstream keyfile;
 	putpath = path;
 	putname = pathToFilename(path);
 	putfile.open(path, std::ios::ate | std::ios::binary);
 	if (putfile.is_open()) {
 		size_t size = putfile.tellg();
+		//~ putsize = size;
+		// increment by one for CCATs signature in its own chunk
+		//~ putchunks = 1 + (size / max_read_len + ((size % max_read_len) ? 1 : 0));
+		if (keyenabled) {
+			// fill IV for file
+			keyfile.open("/dev/urandom", std::ios::binary | std::ios::in);
+			keyfile.read((char *)iv, sizeof(iv));
+			keyfile.close();
+		}
+		putfile.seekg(0);
+
+		// read into memory and chunkify
+
+		char *temp = new char[size + sizeof(signature)];
+		memcpy(temp, signature, sizeof(signature));
+		putfile.read(temp + sizeof(signature), size);
+
+		size += sizeof(signature);
+
+		// have all file in memory prepended with signature
+		// if crypto enabled, encrypt now
+		if (keyenabled) {
+			if (!initCryptoE()) {
+				// failed to init crypto, do not continue
+				delete[] temp;
+				return false;
+			}
+			// resize buffer to also fit IV and tag
+			size_t additionalsize = sizeof(iv) + sizeof(tag);
+			unsigned char *cipher = new unsigned char[size];
+			int cipherlen;
+
+			if (!EVP_EncryptUpdate(cryptctxe, cipher, &cipherlen, (unsigned char *)temp, size)) {
+				setOpensslError();
+				std::cerr << __PRETTY_FUNCTION__ << " failed to update " << getOpensslError() << std::endl;
+			}
+			if (!EVP_EncryptFinal_ex(cryptctxe, cipher + cipherlen, &cipherlen)) {
+				setOpensslError();
+				std::cerr << __PRETTY_FUNCTION__ << " failed to finalize " << getOpensslError() << std::endl;
+			}
+			// obtain tag
+			if (!EVP_CIPHER_CTX_ctrl(cryptctxe, EVP_CTRL_GCM_GET_TAG, 16, tag)) {
+				setOpensslError();
+				std::cerr << __PRETTY_FUNCTION__ << " failed to get tag " << getOpensslError() << std::endl;
+			}
+
+			delete[] temp;
+			temp = new char[size + additionalsize];
+
+			// prepend IV and tag
+			memcpy(temp, iv, sizeof(iv));
+			memcpy(temp + sizeof(iv), tag, sizeof(tag));
+			memcpy(temp + additionalsize, cipher, size);
+
+			delete[] cipher;
+
+			// increase size to also include IV and tag
+			size += sizeof(iv) + sizeof(tag);
+			deinitCryptoE();
+		}
+
+		// chunkify
+		putdata = chunkify(temp, size);
 		putsize = size;
-		putchunks = size / max_read_len + ((size % max_read_len) ? 1 : 0);
-		putchunksRemaining = putchunks;
-		putfile.seekg(std::ios::beg);
+		putchunksRemaining = putchunks = putdata.size();
+
+		delete[] temp;
+
+		// end read into memory and chunkify
+
+		isputting = true;
+
 		return true;
 	}
 	return false;
 }
 
-bool FileMan::openGet(const std::string &path) {
+bool FileMan::openGet(const string &path) {
 	getpath = path;
 	getname = pathToFilename(path);
 	getchunks = 0;
 	getchunksRemaining = 0;
-	getfile.open(path, std::ios::app | std::ios::binary);
+	getfile.open(path, std::ios::app | std::ios::binary | std::ios::out);
 	if (getfile.tellp() != std::ios::beg) {
 		closeGet();
 		return false;
@@ -47,17 +149,59 @@ bool FileMan::openList() {
 	if (isListing()) {
 		return false;
 	}
-	listdata = std::vector<std::string>();
+	listdata = vector<string>();
 	islisting = true;
 	return true;
 }
 
-void FileMan::closePut() { putfile.close(); }
+bool FileMan::openKey(const string &path) {
+	std::ifstream keyfile;
+	std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+	if (isPutting() || isGetting())
+		return false; // do not enable key mid-operation
+	if (keyenabled)
+		closeKey();
+	keyfile.open(path, std::ios::ate | std::ios::binary);
+	std::cerr << __PRETTY_FUNCTION__ << " open keyfile" << std::endl;
+	if (keyfile.is_open()) {
+		std::cerr << __PRETTY_FUNCTION__ << " keyfile open " << keyfile.tellg() << " " << sizeof(key) << std::endl;
+		if (keyfile.tellg() == sizeof(key)) {
+			keyfile.seekg(0);
+			std::cerr << "keyfile is at " << keyfile.tellg() << std::endl;
+			keyfile.read((char *)key, sizeof(key));
+			keyfile.close();
+			keyenabled = true;
+			return true;
+		}
+	}
+	return false;
+}
+
+void FileMan::closePut() {
+	putdata = vector<vector<char>>();
+	isputting = false;
+	memset(iv, 0, sizeof(iv));
+	memset(tag, 0, sizeof(tag));
+}
 
-void FileMan::closeGet() { getfile.close(); }
+void FileMan::closeGet() {
+	getfile.close();
+	memset(iv, 0, sizeof(iv));
+	memset(tag, 0, sizeof(tag));
+}
 
 void FileMan::closeList() { islisting = false; }
 
+bool FileMan::closeKey() {
+	if (isPutting() || isGetting())
+		return false;
+	if (keyenabled) {
+		memset(key, 0, sizeof(key));
+		keyenabled = false;
+	}
+	return true;
+}
+
 void FileMan::cancelPut() {
 	if (isPutting()) {
 		closePut();
@@ -77,19 +221,33 @@ void FileMan::cancelList() {
 	}
 }
 
-void FileMan::writeGet(const std::vector<char> data) {
-	getfile.write(data.data(), data.size());
+void FileMan::writeGet(const vector<char> data) {
+	if (getchunksRemaining == getchunks - 1 && !keyenabled) {
+		// check if signature matches
+		if (memcmp(data.data(), signature, 4)) {
+			// mismatch, encrypted file without enabled key, write as is
+			getfile.write(data.data(), data.size());
+		} else {
+			// skip signature for unencrypted files
+			getfile.write(data.data() + 4, data.size() - 4);
+		}
+	} else {
+		getfile.write(data.data(), data.size());
+	}
 	getchunksRemaining--;
 }
 
-void FileMan::writeBase64(std::string data) { writeGet(base64::decodeVector(data)); }
+void FileMan::writeBase64(string data) {
+	vector<char> decode = base64::decodeVector(data);
+	keyenabled ? writeEnc(decode) : writeGet(decode);
+}
 
-void FileMan::putListData(std::vector<std::string> names) {
+void FileMan::putListData(vector<string> names) {
 	listdata.insert(listdata.end(), names.begin(), names.end());
 	listchunksRemaining--;
 }
 
-std::vector<std::string> FileMan::getListData() { return listdata; }
+vector<string> FileMan::getListData() { return listdata; }
 
 int FileMan::getGetChunks() { return getchunks; }
 
@@ -105,28 +263,17 @@ void FileMan::setListChunks(int chunks) {
 	listchunksRemaining = chunks - 1;
 }
 
-std::vector<char> FileMan::readPut() {
-	char buf[max_read_len];
-	std::vector<char> data;
-
-	std::streamoff read = this->putfile.tellg();
-	if (read + max_read_len > this->putsize) {
-		read = this->putsize % max_read_len;
-	} else {
-		read = max_read_len;
-	}
-
-	putfile.read(buf, read);
-	data.assign(buf, buf + read);
+vector<char> FileMan::readPut() {
+	vector<char> data = putdata[putchunks - putchunksRemaining];
 	putchunksRemaining--;
 	return data;
 }
 
-std::string FileMan::readBase64() { return base64::encodeVector(readPut()); }
+string FileMan::readBase64() { return base64::encodeVector(readPut()); }
 
-std::string FileMan::getPutName() { return putname; }
+string FileMan::getPutName() { return putname; }
 
-std::string FileMan::getGetName() { return getname; }
+string FileMan::getGetName() { return getname; }
 
 int FileMan::getPutChunks() { return putchunks; }
 
@@ -138,11 +285,11 @@ int FileMan::getListRemainingChunks() { return listchunksRemaining; }
 
 int FileMan::getListChunks() { return listchunks; }
 
-std::string FileMan::pathToFilename(std::string path) {
+string FileMan::pathToFilename(string path) {
 
 	int lastFoundIndex = -1;
 
-	for (int currentIndex = path.find("/"); currentIndex != std::string::npos; currentIndex = path.find("/", currentIndex + 1)) {
+	for (int currentIndex = path.find("/"); currentIndex != string::npos; currentIndex = path.find("/", currentIndex + 1)) {
 
 		// check if the "/" was escaped
 		if (currentIndex > 0 && path[currentIndex - 1] == '\\')
@@ -162,3 +309,122 @@ std::string FileMan::pathToFilename(std::string path) {
 
 	return path.substr(lastFoundIndex + 1);
 }
+
+string FileMan::getOpensslError() {
+	pendingerr = false;
+	return opensslerr;
+}
+
+void FileMan::setOpensslError() {
+	opensslerr = ERR_error_string(ERR_get_error(), NULL);
+	pendingerr = true;
+}
+
+bool FileMan::initCryptoE() {
+	// try to initialize crypto context
+	if (!cryptoreadye) {
+		std::cerr << __PRETTY_FUNCTION__ << " init crypto" << std::endl;
+		if (!(cryptctxe = EVP_CIPHER_CTX_new())) {
+			setOpensslError();
+			return false;
+		}
+		if (!EVP_EncryptInit_ex(cryptctxe, EVP_aes_256_gcm(), NULL, key, iv)) {
+			setOpensslError();
+			return false;
+		}
+		cryptoreadye = true;
+	}
+	return true;
+}
+
+bool FileMan::initCryptoD() {
+	// try to initialize crypto context
+	if (!cryptoreadyd) {
+		std::cerr << __PRETTY_FUNCTION__ << " init crypto" << std::endl;
+		if (!(cryptctxd = EVP_CIPHER_CTX_new())) {
+			setOpensslError();
+			return false;
+		}
+		if (!EVP_DecryptInit_ex(cryptctxd, EVP_aes_256_gcm(), NULL, key, iv)) {
+			setOpensslError();
+			return false;
+		}
+		cryptoreadyd = true;
+	}
+	return true;
+}
+
+void FileMan::deinitCryptoE() {
+	if (cryptoreadye) {
+		EVP_CIPHER_CTX_free(cryptctxe);
+		cryptctxe = NULL;
+		cryptoreadye = false;
+	}
+}
+
+void FileMan::deinitCryptoD() {
+	if (cryptoreadyd) {
+		EVP_CIPHER_CTX_free(cryptctxd);
+		cryptctxd = NULL;
+		cryptoreadyd = false;
+	}
+}
+
+void FileMan::writeEnc(const vector<char> data) {
+	std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+	writeGet(data);
+	if (getchunksRemaining < 0) {
+		// loaded everything, try to decrypt
+		unsigned char *cipher, *plain;
+		int plainlen = 0, finallen = 0;
+		getfile.close();
+		getfile.open(getpath, std::ios::binary | std::ios::in | std::ios::ate);
+		size_t size = getfile.tellg();
+		if (size < (sizeof(iv) + sizeof(tag))) {
+			// avoid underflow with files that are too small
+			return;
+		}
+		size -= (sizeof(iv) + sizeof(tag));
+		std::cerr << __PRETTY_FUNCTION__ << " size is " << size << std::endl;
+		cipher = new unsigned char[size];
+		plain = new unsigned char[size];
+		getfile.seekg(0);
+		getfile.read((char *)iv, sizeof(iv));
+		getfile.read((char *)tag, sizeof(tag));
+		getfile.read((char *)cipher, size);
+		getfile.close();
+
+		if (initCryptoD()) {
+			if (!EVP_DecryptUpdate(cryptctxd, plain, &plainlen, cipher, size)) {
+				setOpensslError();
+				std::cerr << __PRETTY_FUNCTION__ << " failed to update " << getOpensslError() << std::endl;
+			}
+			std::cerr << __PRETTY_FUNCTION__ << " decrypted" << std::endl;
+			if (!EVP_CIPHER_CTX_ctrl(cryptctxd, EVP_CTRL_GCM_SET_TAG, 16, tag)) {
+				setOpensslError();
+				std::cerr << __PRETTY_FUNCTION__ << " failed to finalize " << getOpensslError() << std::endl;
+			}
+			std::cerr << __PRETTY_FUNCTION__ << " set tag" << std::endl;
+			if (0 < EVP_DecryptFinal_ex(cryptctxd, plain + plainlen, &finallen)) {
+				plainlen += finallen;
+				std::cerr << __PRETTY_FUNCTION__ << " finalized with len " << plainlen << std::endl;
+				getfile.close();
+				// check signature
+				if (memcmp(plain, signature, 4)) {
+					std::cerr << __PRETTY_FUNCTION__ << " signature mismatch" << std::endl;
+				} else {
+					// signatur matches, skip it and dump the rest to disk
+					getfile.open(getpath, std::ios::binary | std::ios::out | std::ios::trunc);
+					getfile.write((char *)(plain + sizeof(signature)), plainlen - sizeof(signature));
+					getfile.close();
+				}
+			} else {
+				setOpensslError();
+				std::cerr << __PRETTY_FUNCTION__ << " failed to finalize " << getOpensslError() << std::endl;
+			}
+			deinitCryptoD();
+		}
+		delete[] cipher;
+		delete[] plain;
+	}
+}

+ 1 - 1
cli/src/main.cpp

@@ -17,7 +17,7 @@ void show_help(char *exec) {
 }
 
 int main(int argc, char **argv) {
-	bpo::options_description desc{"Options"};
+	bpo::options_description desc("Options");
 	desc.add_options()("help", "show this help")("machine", "switch to machine mode for I/O (not designed for user interaction)")(
 	    "batch", bpo::value<std::string>(), "run operations from arg as batch file")("usessl", "enable ssl for connection to server");
 

+ 16 - 0
cli/src/userioman.cpp

@@ -35,6 +35,8 @@ UserIoMan::UserIoMan(char *ipcstring, bool usessl) : IoMan(ipcstring, usessl) {
 	printmap["getdata"] = &UserIoMan::printGetdata;
 	printmap["head"] = &UserIoMan::printHead;
 	printmap["deleteme"] = &UserIoMan::printDeleteme;
+	printmap["keyfile"] = &UserIoMan::printKeyfile;
+	printmap["closekey"] = &UserIoMan::printClosekey;
 }
 
 UserIoMan::~UserIoMan() { delete reader; }
@@ -187,3 +189,17 @@ void UserIoMan::printHead(Json::Value root) {
 	else
 		std::cout << "First four bytes of file " << root["file"].asString() << " are: " << root["data"].asString() << std::endl;
 }
+
+void UserIoMan::printKeyfile(Json::Value root) {
+	if (!root["accept"].asBool())
+		std::cout << "Couldnt select keyfile " << root["file"].asString() << ": " << root["error"].asString() << std::endl;
+	else
+		std::cout << "Using keyfile " << root["file"].asString() << std::endl;
+}
+
+void UserIoMan::printClosekey(Json::Value root) {
+	if (!root["accept"].asBool())
+		std::cout << "Failed to close key: " << root["error"].asString() << std::endl;
+	else
+		std::cout << "Key closed." << std::endl;
+}

+ 251 - 0
cli/test/cryptotest-gcm.c

@@ -0,0 +1,251 @@
+#include <openssl/conf.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <stdio.h>
+#include <string.h>
+
+// based on OpenSSL sample
+// refer to https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption
+
+void handleErrors(void);
+int gcm_encrypt(unsigned char *plaintext, int plaintext_len,
+                unsigned char *key,
+                unsigned char *iv, int iv_len,
+                unsigned char *ciphertext,
+                unsigned char *tag);
+int gcm_decrypt(unsigned char *ciphertext, int ciphertext_len,
+                unsigned char *tag,
+                unsigned char *key,
+                unsigned char *iv, int iv_len,
+                unsigned char *plaintext);
+
+int main (int argc, char** argv)
+{
+    /*
+     * Set up the key and iv. Do I need to say to not hard code these in a
+     * real application? :-)
+     */
+	
+	FILE *f, *o;
+	unsigned char signature[4] = { 'C', 'C', 'A', 'T' };
+	unsigned char key[32];
+	unsigned char iv[12];
+	unsigned char tag[16];
+	unsigned char *plain;
+	unsigned char *cipher;
+	unsigned insize, mode;
+	int decryptedtext_len, ciphertext_len;
+	if(argc < 4) {
+		printf("not enough args. use %s mode key in out\n", argv[0]);
+		return 1;
+	}
+	
+	mode = strtoul(argv[1], NULL, 10);
+	
+	if(!(f = fopen(argv[2], "rb"))) {
+		printf("cannot open key\n");
+		return 1;
+	}
+	fread(key, sizeof(key), 1, f);
+	fclose(f);
+	
+	if(!(f = fopen("/dev/urandom", "rb"))) {
+		printf("cannot open urandom\n");
+		return 1;
+	}
+	fread(iv, sizeof(iv), 1, f);
+	fclose(f);
+	
+	if(!(f = fopen(argv[3], "rb"))) {
+		printf("cannot open in\n");
+		return 1;
+	}
+	if(!(o = fopen(argv[4], "wb"))) {
+		printf("cannot open out\n");
+		return 1;
+	}
+	
+	
+	if(mode) {
+		fseek(f, 0, SEEK_END);
+		insize = ftell(f)+4;
+		fseek(f, 0, SEEK_SET);
+		plain = malloc(insize);
+		cipher = malloc(insize);
+		fread(plain+4, insize, 1, f);
+		fclose(f);
+		/* prepend signature */
+		memcpy(plain, signature, 4);
+		/* Encrypt the plaintext */
+		ciphertext_len = gcm_encrypt(plain, insize,
+					 key,
+					 iv, sizeof(iv),
+					 cipher, tag);
+		fwrite(iv, sizeof(iv), 1, o);
+		fwrite(tag, sizeof(tag), 1, o);
+		fwrite(cipher, ciphertext_len, 1, o);
+		fclose(o);
+	}
+	else {
+		fseek(f, 0, SEEK_END);
+		insize = ftell(f)-sizeof(iv)-sizeof(tag);
+		fseek(f, 0, SEEK_SET);
+		cipher = malloc(insize);
+		plain = malloc(insize);
+		fread(iv, sizeof(iv), 1, f);
+		fread(tag, sizeof(tag), 1, f);
+		fread(cipher, insize, 1, f);
+		fclose(f);
+		
+		decryptedtext_len = gcm_decrypt(cipher, insize,
+                                    tag,
+                                    key, iv, sizeof(iv),
+                                    plain);
+		if(decryptedtext_len < 0) {
+			printf("decrypt failed\n");
+			fclose(o);
+			remove(argv[4]);
+		}
+		else if(memcmp(plain, signature, 4)) {
+			printf("signature mismatch, expected ");
+			for(int i = 0; i < sizeof(signature); i++) printf("%02x ", signature[i]);
+			printf("but got ");
+			for(int i = 0; i < sizeof(signature); i++) printf("%02x ", plain[i]);
+			printf("\n");
+			fclose(o);
+			remove(argv[4]);
+		}
+		else {
+			fwrite(plain+4, decryptedtext_len-4, 1, o);
+			fclose(o);
+		}
+	}
+	free(cipher);
+	free(plain);
+	printf("done\n");
+	return 0;
+}
+
+
+void handleErrors(void)
+{
+    ERR_print_errors_fp(stderr);
+    abort();
+}
+
+
+int gcm_encrypt(unsigned char *plaintext, int plaintext_len,
+                unsigned char *key,
+                unsigned char *iv, int iv_len,
+                unsigned char *ciphertext,
+                unsigned char *tag)
+{
+    EVP_CIPHER_CTX *ctx;
+
+    int len;
+
+    int ciphertext_len;
+
+
+    /* Create and initialise the context */
+    if(!(ctx = EVP_CIPHER_CTX_new()))
+        handleErrors();
+
+    /* Initialise the encryption operation. */
+    if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
+        handleErrors();
+
+    /*
+     * Set IV length if default 12 bytes (96 bits) is not appropriate
+     */
+    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL))
+        handleErrors();
+
+    /* Initialise key and IV */
+    if(1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv))
+        handleErrors();
+
+    /*
+     * Provide the message to be encrypted, and obtain the encrypted output.
+     * EVP_EncryptUpdate can be called multiple times if necessary
+     */
+    if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
+        handleErrors();
+    ciphertext_len = len;
+
+    /*
+     * Finalise the encryption. Normally ciphertext bytes may be written at
+     * this stage, but this does not occur in GCM mode
+     */
+    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
+        handleErrors();
+    ciphertext_len += len;
+
+    /* Get the tag */
+    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag))
+        handleErrors();
+
+    /* Clean up */
+    EVP_CIPHER_CTX_free(ctx);
+
+    return ciphertext_len;
+}
+
+
+int gcm_decrypt(unsigned char *ciphertext, int ciphertext_len,
+                unsigned char *tag,
+                unsigned char *key,
+                unsigned char *iv, int iv_len,
+                unsigned char *plaintext)
+{
+    EVP_CIPHER_CTX *ctx;
+    int len;
+    int plaintext_len;
+    int ret;
+
+    /* Create and initialise the context */
+    if(!(ctx = EVP_CIPHER_CTX_new()))
+        handleErrors();
+
+    /* Initialise the decryption operation. */
+    if(!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
+        handleErrors();
+
+    /* Set IV length. Not necessary if this is 12 bytes (96 bits) */
+    if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL))
+        handleErrors();
+
+    /* Initialise key and IV */
+    if(!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv))
+        handleErrors();
+
+    /*
+     * Provide the message to be decrypted, and obtain the plaintext output.
+     * EVP_DecryptUpdate can be called multiple times if necessary
+     */
+    if(!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
+        handleErrors();
+    plaintext_len = len;
+
+    /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
+    if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag))
+        handleErrors();
+
+    /*
+     * Finalise the decryption. A positive return value indicates success,
+     * anything else is a failure - the plaintext is not trustworthy.
+     */
+    ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
+
+    /* Clean up */
+    EVP_CIPHER_CTX_free(ctx);
+
+    if(ret > 0) {
+        /* Success */
+        plaintext_len += len;
+        return plaintext_len;
+    } else {
+        /* Verify failed */
+        return -1;
+    }
+}

+ 2 - 0
cli/test/samplekey1.bin

@@ -0,0 +1,2 @@
+�¯]w¿¸{7•ַ´�ֲצ�׀¼gbs€ֱה
+…רee³O7

+ 1 - 0
cli/test/samplekey2.bin

@@ -0,0 +1 @@
+èP`\æS÷Ž”�®¯ÜmÓR‘”tHbjÜ _«]ž}