*and by "ghetto", I mean "extremely applied". Basically, it's just interpretive labor on RFC 5652: Cryptographic Message Syntax and RFC 5280: X.509.
P.S. CMS is way more technically elegant than PGP. I dare you to read the RFC and compare.
Firstly: the "root object" that you'll be shitting nakedly into a binary (or ASCII-armored) file that sits in the filesystem or gets otherwise transferred is a ContentInfo data stream. This is an ASN.1 SEQUENCE with 2 elements:
OBJECT IDENTIFIERgiving "context" and defining what the following element should contain[0] EXPLICIT OCTET STRING OPTIONAL, containing whatever the OID in the first element says to
Before we go on, just take note that AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY DEFINED BY algorithm OPTIONAL }; I didn't want to define terms that aren't directly in a critical chain of logic, but this one is used all the frickin time here so I figured I'd define it for convenience.
We'll generally want the EnvelopedData content type (1.2.840.113549.1.7.3) at the first level; it is a SEQUENCE with 3 to 5 elements:
version(an integer)[0] IMPLICIT OriginatorInfo OPTIONALSET OF CHOICE { KeyTransRecipentInfo, [1] KeyAgreeRecipientInfo, [2] KEKRecipientInfo, [3] PasswordRecipientInfo, [4] OtherRecipientInfo }EncryptedContentInfo[1] IMPLICIT UnprotectedAttributes SET OF AlgorithmIdentifier OPTIONAL- A kinda-comprehensive list of choices can be found here
- Abandon all hope, ye who enter here
- You probably should include at least
id-aa-contentHint(1.2.840.113549.1.9.16.2.4) to specify thecontentTypeof your plaintext data - if this field is present, then the
versionfield must be 2
- A kinda-comprehensive list of choices can be found here
Currently, we'll focus on PasswordRecipientInfo, which was defined in RFC 3211 as a SEQUENCE with 3 to 4 elements:
version(always equal to 0)keyDerivationAlgorithm [0] AlgorithmIdentifier OPTIONAL- PBKDF2 (
1.2.840.113549.1.5.12) is a good choice here PBKDF2-params ::= SEQUENCE { salt CHOICE { specified OCTET STRING, otherSource AlgorithmIdentifier }, iterationCount INTEGER, keyLength INTEGER OPTIONAL, prf AlgorithmIdentifier DEFAULT hMAC-SHA1 }
- PBKDF2 (
keyEncryptionAlgorithm AlgorithmIdentifier- officially prescribed is this convoluted algorithm or these mediocre options. I think we can do better than either, these days.
encryptedKey OCTET STRING
EncryptedContentInfo is a SEQUENCE with 2 to 3 elements:
contentType OBJECT IDENTIFIERcontentEncryptionAlgorithm AlgorithmIdentifier- Nonces generally go here, but it's entirely up to whoever defined the selected
algorithm
- Nonces generally go here, but it's entirely up to whoever defined the selected
encryptedContent [0] IMPLICIT OPTIONAL OCTET STRING DEFINED BY contentEncryptionAlgorithm- “
encryptedContentis the result of encrypting the content. The field is optional, [but] if [it] is not present, its intended value must be supplied by other means.”
- “
TODO: SignedData, which is a complicated composite doodad that supports most other use-cases as a subset