Many modern systems require encryption and authentication, to ensure that sensitive material remains private, and that no questions can arise about where the material originated. OpenSSL is a robust, widely used toolkit for the Transport Layer Security (TLS) and Secure Sockets Layer (SSL) protocols, and contains support for general-purpose cryptography.
The GemStone distribution includes OpenSSL executables and libraries, which are used internally within GemStone. For many OpenSSL functions, you may use your own OpenSSL installation if you prefer.
This chapter describes the tools and features that GemStone provides that make working with SSL keys easier.
Overview for SSL keys and certificates
How to create example keys, and the GemStone classes that encapsulated keys and certificates.
Checksums and HMAC
Ways to verifying the consistency of text.
Symmetric-Key Encryption
Encrypting text
Digital Signatures
Digitally signing text.
Digital Envelopes
How to create and use digital envelopes to transmit information with both encryption and signing.
GemStone provides the OpenSSL executable and shared libraries, which can be used to generate keys and support the GemStone features that authenticate SSL.
A full explanation of SSL key algorithms and the choice of which to use, and the details of generating keys, is outside the scope of this document, and SSL and security algorithms are under active development. Simple examples are provided here to illustrate the use of the tools. You should review your application security requirements with the latest security recommendations and ensure that your security is sufficiently strong. GemStone provides access to the OpenSSL security tools; you are responsible for using these tools correctly such that you are using encryption and authentication that is appropriate for your needs.
For applications that are not limited to internal, secure networks, security is a critical. The usage in these examples is NOT sufficient to provide complete security. |
The openssl executable enables you to generate a private key. A public key can always be extracted from a private key. The following lines will create a private key and extract the public key, providing the public/private key pairs used in the examples.
unix> $GEMSTONE/bin/openssl genrsa -out privKey.pem 2048
unix> $GEMSTONE/bin/openssl rsa -in privKey.pem -pubout -out pubKey.pem
An X509 certificate contains a public SSL key as well as validation information, so the public key can be extracted from an X509 certificate file.
The classes GsTlsCredential, with subclasses GsTlsPrivateKey, GsTlsPublicKey, and GsX509Certificate, encapsulate TLS (SSL) private keys, public keys, and X509 certificates, respectively. Instances contain a hidden reference to C pointer to the OpenSSL representation of the TLS object.
Instances of GsTlsPublicKey and GsTlsPrivateKey are used for digital signing and digital envelopes, as well as in X509-Secured external sessions.
Instances are created by reading the PEM from a file or String. The class methods
newFromPemFile:
newFromPemString:
Private keys, which may have an optional (but generally recommended) passphrase, have additional methods:
GsTlsPrivateKey >> newFromPemFile:withPassphrase:
GsTlsPrivateKey >> newFromPemFile:withPassphraseFile:
GsTlsPrivateKey >> newFromPemString::withPassphrase:
GsTlsPrivateKey >> newFromPemString::withPassphraseFile:
These methods can used to instantiate subclasses of GsTlsCredential from string or disk file arguments. For example,
GsTlsPublicKey newFromPemFile: 'pubKey.pem'
You can get the public key from an instance of GsTlsPrivateKey or GsX509Certificate by sending asPublicKey.
The following method can be used to verify that two keys or X509 certificates form a matching public/private key pair:
matches: anotherKey
Return true if the receiver and anotherKey match each other as a valid public key-private key or certificate pair.
RSA and DSA key pairs match if both keys use the same modulus. Elliptic curve key pairs match if both keys use the same curve and the same point on that curve.
OpenSSL 1.1.1 supports a number of encryption and signing algorithms. To query an instance of a kind of GsTlsCredential for the algorithm, the following methods are available:
algorithm
Answers a Symbol indicating the type of high-level PKI (Public Key Infrastructure) algorithm the receiver uses. The high-level PKI algorithms supported are:
#RSA - Rivest-Shamir-Adleman
#DSA - Data Signature Algorithm
#EC - Elliptic Curve Cryptography
All high-level algorithms have various sub-types. Use the sslAlgorithm method to obtain information about the specific PKI algorithm of the receiver.
sslAlgorithm
Answers a Symbol indicating the SSL type of PKI algorithm the receiver uses. See the image comment for details.
The methods are of the form algorithmSum to compute the numeric checksum, and asalgorithmString to compute the checksum as a string. See the image for the full list of methods on classes ByteArray and CharacterCollection.
aByteArray md5Sum
aString asSha1String
For Strings or ByteArrays, a hash-based authentication code can be computed. This can be returned as a LargeInteger or as a string of hex digits and letters.
The HMAC can be computed with:
HMAC uses a secret key, which must be a single byte object: a String, ByteArray or Unicode7.
See the image for the full list of methods on classes ByteArray and Character.
'Fourscore and seven years ago our fathers brought forth on
this continent a new nation, conceived in liberty and dedicated
to the proposition that all men are created equal'
asMd5HmacWithKey: 'Lincoln'
%
198049353258401870159700056328212954477
'It was the best of times, it was the worst of times, it was
the age of wisdom, it was the age of foolishness, it was the
epoch of belief, it was the epoch of incredulity'
asSha3_224HmacStringWithKey: 'Dickens'
%
'a80a577c5ca6d29455a7ad168d92b91b59242907f9a254696e8675ce'
Symmetric key encryption uses the same key to perform the encryption and the decryption (in contrast to asymmetric-key encryption, which use separate public and private keys).
AES encryption/decryption (Advanced Encryption Standard) is a block symmetric cipher, while ChaCha20 is a stream symmetric cipher.
OCB, GCM and Poly1305 are Authenticated Encryption with Associated Data (AEAD) modes. AEAD provides data authenticity, confidentiality, and integrity.
AEAD also supports Additional Authenticated Data (AAD). AAD is not encrypted and therefore not confidential, but its authenticity and integrity are guaranteed. If AAD is used, it is not included in the encrypted payload, but must be provided in order to decrypt the data. The additional data is optional, so the argument for this may be nil, in which cases it is not needed for decryption.
Both kinds of CharacterCollection (Strings and Unicode Strings) and ByteArrays can be encrypted.
AlgorithmEncryptWithNNNBitKey: aKey salt: aSalt into: destObjOrNil tag: aTag extraData: eData
AlgorithmDecryptWithNNNBitKey: aKey salt: aSalt into: destObjOrNil tag: aTag extraData: eData
These methods encrypt or decrypt the receiver, respectively, using NNN bits and the algorithm Algorithm.
See the image for the specific methods that are available. The example below uses aesOcbEncryptWith256BitKey:salt:into:tag:extraData: and aesOcbDecryptWith256BitKey:salt:into:tag:extraData:
destObjOrNil must be nil or an instance of a byte object (that is not invariant). If destObjOrNil is nil, the result of the operation will be placed into a new instance of ByteArray (encryption) or String (decryption); otherwise the result will be placed into the given byte object starting at offset 1.
The size of destObjOrNil will be modified to correctly contain all encrypted or decrypted data, and may differ from the size of the receiver due to the automatic addition or removal of padding by the cipher algorithm.
aSalt must be a ByteArray of size 12. aKey must be a ByteArray with the appropriate size for the method. The same key and salt must be used to decrypt as were used to encrypt.
During AEAD encryption, a tag is generated which is used during decryption to ensure data integrity. The tag data will be stored into the aTag argument, which must an instance of a byte object. The extra data eData must be nil or a byte object with a character size of one (a ByteArray, String, or Unicode7) containing additional data to be used in generating the tag value. On decryption, the tag argument aTag must be the bytes generated during encryption, and the same bytes of eData must be provided, or nil.
When encrypting a receiver that a character size greater than one, data is placed into big-endian byte order before encryption. On decryption into a destObjOrNil object that has a character size greater than one, data is converted to big-endian byte order after decrypting.
The following code encrypts the String in textToBeEncrypted using AES-OCB with 256 bits, which requires a 32-byte key.
| textToBeEncrypted myKey mySalt myTag encoded decoded |
textToBeEncrypted := 'In sooth, I know not why I am so sad'.
myKey := ByteArray withRandomBytes: 32.
mySalt := ByteArray withRandomBytes: 12.
myTag := ByteArray new.
encoded := textToBeEncrypted aesOcbEncryptWith256BitKey: myKey
salt: mySalt into: nil tag: myTag extraData: 'shakespeare'.
decoded := encoded aesOcbDecryptWith256BitKey: myKey
salt: mySalt into: nil tag: myTag extraData: 'shakespeare'.
Digitally signing allows data (in the form of a String or ByteArray) to be transmitted to a remote destination, and the receiver to be certain that the data originated with the receiver and has not been altered during transmission. Signing data does not encrypt it.
The originating server signs the data using their private key. The originating server’s public key can be used by any recipient to verify the signature.
A number of signing algorithms are supported:
Sha256AndRsaPss (PSS padding ) |
Sha3_256AndRsaPss (PSS padding ) Sha3_256AndRsa (PKCS1 padding ) Sha3_384AndRsaPss (PSS padding ) Sha3_384AndRsa (PKCS1 padding ) Sha3_512AndRsaPss (PSS padding ) |
Data is signed on the originating server with a method of the form:
signWithAlgorithmAndKeyTypePaddingPrivateKey: aGsTlsPrivateKey into: aByteArrayOrNil
Hashes the receiver using Algorithm and signs the resulting hash with the KeyType and Padding. Returns a ByteArray containing the resulting signature.
After transmission to the destination, it is verified with the matching method of the form:
verifyWithAlgorithmAndKeyTypePaddingPublicKey: aGsTlsPublicKey signature: aByteArray
Hashes the receiver using Algorithm and verifies the resulting hash using the KeyType and Padding. Returns true if the signature is correct.
signWithSha3_256AndRsaPssPrivateKey:into:
verifyWithSha3_256AndRsaPublicKey:signature:
signWithSha512AndDsaPrivateKey:into:
verifyWithSha512AndDsaPublicKey:signature:
GsDigitalEnvelopes allow both encryption and signing in a single operation. A GsDigitalEnvelope has the following security features:
NOTE: In order to guarantee authentication, the receiver must confirm that the public verification key actually belongs to the sender. Normally this confirmation is done by verifying that an X509 certificate containing the sender's public key has been signed by a reputable certificate authority.
GsDigitalEnvelope does NOT do this public key/certificate signature verification; it is up to the envelope’s recipient to ensure that the public key or X509 certificate used to verify the signature is trustworthy.
Digital Envelopes are created using the following methods:
GsDigitalEnvelope class >> encryptMessage: messageBytes
withPublicEncryptionKey: publicEncryptionKey
cipherId: cipherOpCode
withPrivateSigningKey: privateSigningKey
GsDigitalEnvelope class >> encryptMessage: messageBytes
withPublicEncryptionKeys: arrayOfPublicEncryptionKey
cipherId: cipherOpCode
withPrivateSigningKey: privateSigningKey
The message to be encrypted may be any kind of String or ByteArray. The name of the class is stored in the envelope so when the envelope is opened, an instance of the same kind of object is returned. Internally, if the message is a multi-byte String, the string is converted into big-endian form, but this is transparent to the user. It is an error if the class of object that was encrypted into the envelope is not resolvable at the destination.
publicEncryptionKey and privateSigningKey
The message is encrypted using a public encryption key, and signed using a private signing key, which should be distinct. The envelope can only be "opened" (decrypted) by a recipient with the private key that matches the public encryption key, and the public key that matches the private signing key.
Providing an array of multiple encryption keys allows a message to encrypted by multiple public keys into multiple envelopes in one operation. Each envelope can be opened by the single matching private key, and the public signing key.
The encryption and signing keys can be instances of GsTlsPublicKey or GsX509Certificate. Encryptions keys must be RSA; signing keys may be DSA, RSA, Ed25519, ECDSA, EC, or Ed448.
For signing algorithms that require a message digest algorithm, the SHA2-256 message digest is used. Signatures generated with RSA keys are padded using the RSA_PKCS1_PSS_PADDING scheme.
The cipherId: argument accepts a numeric code specifying the AEAD (Authenticated Encryption with Additional Data) of the cipher; legal values are in the following table.
Ciphers corresponding to 6 and 10 are considered the most secure and are recommended for most applications.
Support has also been added to allow GsDigitalEnvelopes to be passivated into string form, for ease of transmission to the destination.
Once a GsDigitalEnvelope object is created, it can be converted to a passive object (String) for transmission to the recipient. Decrypting ("opening") the given envelope requires the private key matching the argument public key, and the public signing key matching the private key used to sign the envelope. The recipient activates the GsDigitalEnvelope object, and then opens it using the matching keys.
A detailed example is provided in class methods in GsDigitalEnvelope. The example follows the following form, first creating the envelope and then passivating it on the originating server, and activating and decryption on the destination.
origEnvelope := GsDigitalEnvelope
encryptMessage: messageText
withPublicKey: aGsTlsPubEncrKey
cipherId: 10
withPrivateSigningKey: aGsTlsPrivSigKey.
aStream := WriteStream on: String new.
PassiveObject passivate: origEnvelope toStream: aStream.
aPassiveString := aStream contents.