Symmetric Cryptography
Stream Ciphers
A stream cipher is a symmetric key cipher processing input data one discrete unit at a time. The incoming data is called plaintext or cleartext, the encrypted output is named ciphertext. The same key is used for both encryption and decryption:
Secret keys are used to create keystreams: encryption is performed by combining the keystream with the plaintext. Most stream ciphers are synchronous: the keystream digits are generated independently of the plaintext and ciphertext data. In another design previous ciphertext digits can be utilized to generate the keystream: such ciphers are called self-synchronizing. Many cipher algorithms require a value altering the input data before encryption: the value is named initialization vector, or IV.
Desktop Tools
A single stream cipher currently supported by OpenSSL is RC4. The algorithm is considered very insecure, so developers wishing to launch stream encryption from the command line should turn to LibreSSL implementation of cryptographic routines: LibreSSL supports ChaCha - a fast and safe variant of Salsa20. Google selected ChaCha20 along with the Poly1305 MAC algorithm as a replacement for RC4 in TLS connections. Examples of TLS cipher suites with ChaCha are ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305 and DHE-RSA-CHACHA20-POLY1305.
In addition to OpenSSL/LibreSSL, Java developers can use keytool - a handy desktop utility from the JDK. Though the tool deals primarily with asymmetric cryptography, it also supports secret key entries. Keys generated by keytool can be passed to a Java application.
OpenSSL
The list of symmetric cipher algorithms implemented in OpenSSL/LibreSSL is returned by the following pseudo-command:
openssl list-cipher-algorithms
Plaintext encryption is performed by the enc command:
openssl enc -ChaCha -in plaintext.pdf -out ciphertext.dat -pass stdin -e -p
The encryption key in the demo above is derived from the user-supplied password: the value of the -pass option tells the cryptographic engine to read the passphrase from the command line. The -e option stands for 'encryption'. The -p option prints out the generated key and initialization vector as hexadecimal strings.
OpenSSL allows the user to employ the name of a stream or block cipher as an independent command:
openssl ChaCha -in plaintext.pdf -out ciphertext.dat -pass stdin -e -p
The list of such commands is retrieved by
openssl list-cipher-commands
Decipherment is requested by the -d option:
openssl enc -ChaCha -in ciphertext.dat -out plaintext.pdf -pass stdin -d
A key and an initialization vector can be declared explicitly as the values of the -K and -iv options:
export key=41270a3dbd3c27f31f44d819fe023559fdcefd508042d34836894ee76f9f5f82
export iv=2cb6529582a30b97
openssl enc -ChaCha -in ciphertext.dat -out plaintext.pdf -e -K $key -iv $iv
keytool
keytool manages a store of cryptographic keys and X.509 certificates. Keystore entries accessed via unique aliases keep cryptographic material of symmetric and asymmetric keys.
The -genseckey command builds a secret key entry in a store:
keytool -genseckey -alias ChaCha -keyalg ChaCha -keysize 256 -storetype bks -keystore keys.bks -providerClass org.bouncycastle.jce.provider.BouncyCastleProvider
The command in the demo above makes use of the proprietary keystore implementation (bks) provided by BouncyCastle and accepts an extended set of options:
- -alias declaring the name of the keystore entry to hold the generated secret key;
- -keyalg - the standard algorithm name suitable for the KeyGenerator engine class;
- -keysize setting the key length in bits (256);
- -storetype - the type of the keystore;
- -keystore specifying the name of the keystore file;
- -providerClass with the fully qualified name of the BouncyCastle CSP.
The default value of the -storetype option depends on Java security properties: if the user has not modified the java.security
file, the default keystore type is jks.
The keystore is protected: the user is prompted to enter a password when the store is being created. Another password is asked from the user to encrypt the generated key material.
The -list command with the -v (verbose) option displays keystore entries information:
keytool -list -alias ChaCha -storetype bks -keystore keys.bks -providerClass org.bouncycastle.jce.provider.BouncyCastleProvider -v
Programming Languages
Stream ciphers are not so broadly implemented in software as block cipher algorithms: as a rule, insecure RC4 is the only stream algorithm that is supported by core libraries of a programming language. To obtain access to more reliable ciphers, special cryptographic packages should be brought into play. Additionally, functionality of high-level languages can be extended by C/C++: for example, stream cipher routines based on LibreSSL or Crypto++ are made available to Java applications through the JNI mechanism.
Stream Ciphers in Java
Java developers wishing to use stream ciphers in their applications will inevitably resort to BouncyCastle software supporting such algorithms as ChaCha, Grain, HC-128, HC-256, ISAAC, RC4, Salsa20 and VMPC. We'll illustrate the main principles of symmetric encryption in Java with the ChaCha stream cipher.
Secret Key Generation
The KeyGenerator engine class is required for creating a secret key. A generator object is constructed by invoking the getInstance() method:
KeyGenerator keyGenerator = KeyGenerator.getInstance("ChaCha", "BC");
The static factory method above has accepted two arguments - the name of the requested algorithm and the name of the BouncyCastle provider.
The next step is initialization of the generator with the key length in bits:
keyGenerator.init(256);
The generateKey() method returns an instance of the SecretKey:
SecretKey secretKey = keyGenerator.generateKey();
The SecretKey is a subinterface of the top-level Key API defining characteristics of both symmetric and asymmetric keys regardless of the key algorithm.
The primary encoding format of the generated secret key is RAW:
String keyFormat = secretKey.getFormat();
The raw key material can be converted to various data formats:
byte[] keyBytes = secretKey.getEncoded();
String hex = Hex.toHexString(keyBytes);
String b64 = Base64.toBase64String(keyBytes);
KeyStore With Secret Key Entries
A key generator is not the only way to obtain a secret key for encryption. If a key has been created and saved by the keytool utility, it can be extracted via a KeyStore object:
KeyStore keyStore = KeyStore.getInstance("BKS", "BC");
FileInputStream stream = new FileInputStream("keys.bks");
keyStore.load(stream, "keystore_password".toCharArray());
A secret key entry is retrieved by its alias:
KeyStore.PasswordProtection protection = new PasswordProtection("entry_password".toCharArray());
KeyStore.SecretKeyEntry entry = (KeyStore.SecretKeyEntry) keyStore.getEntry("ChaCha", protection);
SecretKey secretKey = entry.getSecretKey();
For brevity, the code above shows the passphrases as plain strings. In real-world applications, however, passwords must not be hard-coded: a special routine for asking a password from the user should be implemented. A passphrase in a desktop utility might be read from the standard input by calling the readPassword() method of the Console object.
Initialization Vector
ChaCha encryption requires both a secret key and an 8-byte initialization vector. The IvParameterSpec class provides a transparent specification of the IV:
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[8];
secureRandom.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Similar to the secret key, the generated initialization vector can be converted to the hexadecimal or Base64-encoded string:
String hexIV = Hex.toHexString(iv);
String b64IV = Base64.toBase64String(iv);
Cipher Engine Class
Now that the secret key and the IV are prepared, a Cipher engine can be instantiated:
Cipher cipher = Cipher.getInstance("ChaCha", "BC");
The Cipher object must be initialized to encryption mode:
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
Single-part encryption is performed by calling the doFinal() method:
byte[] ciphertext = cipher.doFinal("plaintext".getBytes());
Before decryption, the Cipher is initialized with the same key and IV parameters:
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
The doFinal() method in decryption mode deciphers the encrypted data:
byte[] plaintext = cipher.doFinal(ciphertext);
Cipher Streams
CipherInputStream and CipherOutputStream classes resemble Java digest streams: symmetric encryption/decryption is a filtering operation performed on an underlying source of data. A Cipher object must be fully initialized before being passed to the stream constructor.
CipherOutputStream
The demo application below will exemplify the use of cipher streams in combination with JDBC APIs. Let's assume the user works with confidential files on his local machine, then uploads them to a Derby network server. Any mature DBMS supports various security mechanisms including disk encryption and user authentication/authorization. However, if an intruder knows passwords and user identifiers he can get illegal access to the database contents. Additional encryption of a DB record created by a certain user can be regarded as an auxiliary level of protection: even if the intruder has hacked the database, he will not be able to extract security-sensitive information from the stored files. Encryption keys are kept locally by the client Java application and are employed when the user is about to send a file to the server.
A built-in SQL type for holding files in a database is BLOB. SQL-to-Java mapping provided by JDBC maps SQL Binary Large Objects to the Blob interface. A table with BLOB fields might be created by the following SQL statement:
CREATE TABLE Documents (ID INT NOT NULL PRIMARY KEY, Name VARCHAR(64), File BLOB(20M));
To illustrate exception handling for I/O streams and JDBC objects, the demo code below initializes streams and Derby Connection before the try/catch block, performs application-specific tasks within the try block, then closes streams and releases connection resources in the finally clause of the try statement:
variables for R/W operations
int i;
byte[] buffer = new byte[8192];
stream for reading a local file
BufferedInputStream inputStream = null;
cipher stream for encrypting BLOB
CipherOutputStream cipherOutputStream = null;
object representing a connection to Derby database
Connection connection = null;
try {
cryptographic, database and stream operations
} catch(Exception e) {
System.err.format("Requested operation cannot be performed: %s\n", e.getMessage());
} finally {
closing streams and JDBC connection
}
For brevity's sake, the exception parameter of the catch clause is just an instance of the general Exception class. Proper errors handling, however, may require the use of its various subclasses, e.g. InvalidKeyException, IOException, SQLException, etc.
The first group of cryptographic operations creates a Cipher and initializes it for encryption:
KeyGenerator keyGenerator = KeyGenerator.getInstance("ChaCha", "BC");
keyGenerator.init(256);
SecretKey secretKey = keyGenerator.generateKey();
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[8];
secureRandom.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("ChaCha", "BC");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
Then a local file to be stored in the database is prepared:
File file = new File("top-secret.pdf");
inputStream = new BufferedInputStream(new FileInputStream(file));
To connect to the database server, the application makes use of the DriverManager:
String connectionString = "jdbc:derby://example.com:1527/officedb";
connection = DriverManager.getConnection(connectionString);
The createBlob() method of the Connection builds an object implementing the Blob interface:
Blob blob = connection.createBlob();
The Blob is initially empty. Calling the setBinaryStream() method instantiates a stream for writing binary data to the Blob. The stream and the previously initialized Cipher are passed to a CipherOutputStream:
cipherOutputStream = new CipherOutputStream(blob.setBinaryStream(1), cipher);
Now the application can read bytes from the local file, encrypt them and store the ciphertext in the Blob:
while((i = inputStream.read(buffer)) != -1) {
cipherOutputStream.write(buffer, 0, i);
}
cipherOutputStream.flush();
The Blob is ready for being stored in the database:
System.out.format("Blob of %d bytes has been encrypted and prepared for the database\n", blob.length());
A PreparedStatement object allows the application to send parameterized SQL to the database:
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO Documents VALUES (?, ?, ?)");
An ID of the file, its name and the encrypted Blob will replace parameter markers in the precompiled SQL string:
preparedStatement.setInt(1, 1);
preparedStatement.setString(2, file.getName());
preparedStatement.setBlob(3, blob);
preparedStatement.execute();
If the statement has been executed successfully, the ciphertext is stored in the database as SQL BLOB.
Statements in the finally block should be also supplied with exception handlers:
finally {
closing I/O streams
try {
if(inputStream != null) {
inputStream.close();
}
if(cipherOutputStream != null) {
cipherOutputStream.close();
}
} catch (IOException ioException) {
System.err.format("Closing streams has encountered a problem: %s\n", ioException.getMessage());
}
releasing connection resources
try {
if(connection != null) {
connection.close();
}
} catch (SQLException sqlException) {
System.err.println("Current database connection cannot be closed");
}
}
An SQLException can have a causal relationship consisting of one or more Throwable objects which caused the exception. A chain of causes is extracted by iteration:
try {
if(connection != null) {
connection.close();
}
} catch (SQLException sqlException) {
System.err.println("Current database connection cannot be closed");
for(Throwable throwable : sqlException ) {
System.err.println("Error encountered: " + throwable.getMessage());
}
}
CipherInputStream
CipherInputStream will perform a reverse operation: the encrypted Blob will be retrieved from the database, deciphered and saved on the local machine. We presume that the encryption key and IV are securely kept by the client application. Before decryption, the key can be exposed as a class implementing the SecretKey interface:
private class ChaChaKey implements SecretKey {
@Override
public String getFormat() {
return "RAW";
}
@Override
public byte[] getEncoded() {
return Hex.decode("a301a1b61cf20099f26df9960ad25308bdee6e18627a34c039facea65407e811");
}
@Override
public String getAlgorithm() {
return "ChaCha";
}
}
Once again, streams and JDBC Connection are declared before the try/catch block:
int i;
byte[] buffer = new byte[8192];
stream for writing decrypted data
BufferedOutputStream outputStream = null;
cipher stream for decrypting BLOB
CipherInputStream cipherInputStream = null;
JDBC connection
Connection connection = null;
try {
cryptographic, database and stream operations
} catch(Exception e) {
System.err.format("Requested operation cannot be performed: %s\n", e.getMessage());
} finally {
closing streams and JDBC connection
}
Operations within the try block starts with initializing a Cipher for decryption. The same parameters that were used for encipherment are passed to the init() method:
SecretKey secretKey = new ChaChaKey();
byte[] iv = Hex.decode("e95a2db1af9c3d18");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("ChaCha", "BC");
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
A local file to hold decrypted data is created:
File file = new File("top-secret-from-db.pdf");
outputStream = new BufferedOutputStream(new FileOutputStream(file));
The Connection, Statement and ResultSet objects are necessary to extract the Blob containing encrypted data:
String connectionString="jdbc:derby://example.com/officedb";
connection = DriverManager.getConnection(connectionString);
Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
ResultSet resultSet = statement.executeQuery("SELECT File FROM Documents WHERE Name='top-secret.pdf'");
resultSet.first();
Blob blob = resultSet.getBlob(1);
The getBinaryStream() method returns an input stream for reading the Blob data. A CipherInputStream object filters the stream by decrypting the ciphertext:
cipherInputStream = new CipherInputStream(blob.getBinaryStream(), cipher);
while((i = cipherInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, i);
}
outputStream.flush();
Sealed Objects
In addition to heavyweight Cipher and cipher streams API, Java cryptography has a helpful feature allowing an application to protect any Java object that implements the Serializable interface. Such an object is encapsulated in a SealedObject. The serialized data is encrypted:
Cipher cipher = Cipher.getInstance("ChaCha", "BC");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
SealedObject sealedObject = new SealedObject("plaintext", cipher);
The getObject() method retrieves the encapsulated object:
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
String plaintext = (String) sealedObject.getObject(cipher);
BouncyCastle Cryptography API
As an alternative to standard interfaces based on the Java Cryptography Architecture, developers can turn to the low-level BouncyCastle API. Stream and block ciphers are implemented as classes in the org.bouncycastle.crypto.engines package.
The following example performs ChaCha encryption of a local file.
The initial step is the creation of parameters for a key generator. The constructor of the KeyGenerationParameters accepts a source of randomness and the key length in bits:
SecureRandom secureRandom = new SecureRandom();
KeyGenerationParameters keyGenerationParameters = new KeyGenerationParameters(secureRandom, 256);
A CipherKeyGenerator object parses the aforementioned parameters and produces a secret key as a sequence of bytes:
CipherKeyGenerator cipherKeyGenerator = new CipherKeyGenerator();
cipherKeyGenerator.init(keyGenerationParameters);
byte[] key = cipherKeyGenerator.generateKey();
The ChaCha engine will require both the key and IV parameters:
KeyParameter keyParameter = new KeyParameter(key);
byte[] iv = new byte[8];
secureRandom.nextBytes(iv);
ParametersWithIV parametersWithIV = new ParametersWithIV(keyParameter, iv);
Now that all preliminaries are taken, an instance of the ChaChaEngine is built. The boolean true sets the engine for encryption:
ChaChaEngine chaChaEngine = new ChaChaEngine();
chaChaEngine.init(true, parametersWithIV);
The final step is the reading of the input plaintext and its encipherment:
int i;
byte[] buffer=new byte[8192];
try(
two resources are declared
BufferedInputStream plaintextStream = new BufferedInputStream(new FileInputStream("plaintext.pdf"));
BufferedOutputStream ciphertextStream = new BufferedOutputStream(new FileOutputStream("ciphertext.dat"))
) {
while((i = plaintextStream.read(buffer)) != -1) {
byte[] ciphertext = new byte[i];
chaChaEngine.processBytes(buffer, 0, i, ciphertext, 0);
ciphertextStream.write(ciphertext);
}
}
The processBytes() method accepts the following arguments: the input byte array, the offset into the input array, the number of bytes to be processed, the output array the encrypted bytes go into and the offset into the output array.
The code snippet has demonstrated another way of handling I/O streams: instead of the try/catch block, the application employs the try-with-resources statement: two streams implementing the AutoCloseable interface are disposed of without the explicit call of the close() method.
C#
The same low-level cryptographic API is implemented in the BouncyCastle library for the .NET Framework: the developer uses classes from the Org.BouncyCastle.Crypto.Engines namespace to get access to stream ciphers.
The example below shows Salsa20 encryption of a local file:
generating secret key
SecureRandom secureRandom = new SecureRandom();
KeyGenerationParameters keyGenerationParameters = new KeyGenerationParameters(secureRandom, 256);
CipherKeyGenerator cipherKeyGenerator = new CipherKeyGenerator();
cipherKeyGenerator.Init(keyGenerationParameters);
byte[] key = cipherKeyGenerator.GenerateKey();
creating algorithm parameters
KeyParameter keyParameter = new KeyParameter(key);
byte[] iv = new byte[8];
secureRandom.NextBytes(iv);
ParametersWithIV parametersWithIV = new ParametersWithIV(keyParameter, iv);
stream cipher engine initialization
Salsa20Engine salsa20Engine = new Salsa20Engine();
salsa20Engine.Init(true, parametersWithIV);
file encryption
FileStream inputStream = File.Open("plaintext.pdf", FileMode.Open, FileAccess.Read);
FileStream outputStream = File.Open("ciphertext.dat", FileMode.Create, FileAccess.Write);
int i;
byte[] buffer = new byte[8192];
while((i = inputStream.Read(buffer,0, 8192)) > 0){
byte[] ciphertext = new byte[i];
salsa20Engine.ProcessBytes(buffer, 0, i, ciphertext, 0);
outputStream.Write(ciphertext, 0, i);
}
Similar to Java, a file stream in C# can be wrapped in a BufferedStream:
BufferedStream inputStream = new BufferedStream(File.Open("plaintext.pdf", FileMode.Open, FileAccess.Read));
Stream reading/writing in C# should be used with try-catch-finally statements. Alternatively, stream lifecycle can be controlled by the using statement: it automates the process of acquiring a resource, performing I/O operations and then disposing of the resource:
using (FileStream inputStream = File.Open("plaintext.pdf", FileMode.Open, FileAccess.Read))
{
using(FileStream outputStream = File.Open("ciphertext.dat", FileMode.Create, FileAccess.Write))
{
. . . encryption . . .
}
}
A resource is a class or struct that implements the System.IDisposable interface:
Visual Basic.NET
BouncyCastle library can be used with any language supported by the .NET Framework. The code snippet below demonstrates the same Salsa20 encryption in Visual Basic.NET:
Dim secureRandom As New SecureRandom()
Dim keyGenerationParameters As New KeyGenerationParameters(secureRandom, 256)
Dim cipherKeyGenerator As New CipherKeyGenerator()
cipherKeyGenerator.Init(keyGenerationParameters)
Dim key As Byte() = cipherKeyGenerator.GenerateKey()
Dim keyParameter As New KeyParameter(key)
Dim iv As Byte() = New Byte(7) {}
secureRandom.NextBytes(iv)
Dim parametersWithIV As New ParametersWithIV(keyParameter, iv)
Dim salsa20Engine As New Salsa20Engine()
salsa20Engine.Init(True, parametersWithIV)
Using inputStream As New BufferedStream(File.Open("plaintext.pdf", FileMode.Open, FileAccess.Read))
Using outputStream As New BufferedStream(File.Open("ciphertext.dat", FileMode.Create, FileAccess.Write))
Dim i As Integer
Dim buffer As Byte() = New Byte(8191) {}
While (InlineAssignHelper(i, inputStream.Read(buffer, 0, 8192))) > 0
Dim ciphertext As Byte() = New Byte(i - 1) {}
salsa20Engine.ProcessBytes(buffer, 0, i, ciphertext, 0)
outputStream.Write(ciphertext, 0, i)
End While
End Using
End Using