|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThe Crypto++ mailing list occasionally receives questions regarding importing public and private keys from other libraries, and exporting keys for use in foreign libraries. This article will demonstrate moving RSA key material between Crypto++, C#, and Java. In addition, we will explore the special case of loading RSA and DSA keys in C#, since the CLR presents some interesting issues. On the surface, we would expect that Crypto++ would be the most difficult while C# and Java would be the least difficult. In practice, Crypto++ and Java are the easiest libraries for which we can achieve interoperability. This is due to the CLR's lack of standardized serialization support for key pairs. We observe what a kludge C# can cause when we read articles such as Porting Java Public Key Hash to C# .NET [14]. Developers are forced to deviate from the well established key formats of PKCS #8 and X.509 so that C# can import or export keys using XML as specified in RFC 3275, XML-Signature Syntax and Processing [26]. Sections 4.4.1 and 4.4.2 specify definitions related to DSA (and RSA) parameters such as the We examine the details of the process below. We do this so that when issues occur, we will be able to quickly identify and correct the problem. Along the way, references into various standards are presented for further reading when things do go wrong. Topics to be visited are listed below.
Though an understanding of ASN.1 is required for reading and writing keys, it introduced last since it is only offered for completeness. While visiting other sections which rely on a basic knowledge of ASN.1, understand that it is a presentation layer protocol, like any other presentation layer protocol such as Base64 encoding and decoding. Also, keep in mind that ASN.1 is similar to a programming language — complete with a language, grammar, and productions. Finally, an ASN.1 dumper will prove useful. A graphical tool such as Objective System's ASN.1 View, or a command line tool such as Peter Guttman's dumpasn1 works well. While at Guttman's page, read over the X.509 Style Guide. Take the time to visit Michel Gallant's JavaScience. Dr. Gallant has authored several articles for MSDN in the .NET Cryptography arena, and offers both .NET and Java source code. Also of interest may be Cryptographic Interoperability: Digital Signatures [22], which looks at the issues encountered when using DSA signatures between C++, Java, and C#. DownloadsThere are three downloads which are available at the beginning of the article. Each archive is a project for creating and verifying. For those who only want the source code, Table 1 identifies the download of interest.
PKCS and X.509Public and private RSA keys can be moved between systems using PKCS#1, PKCS#8, and X.509. This section examines the formats defined by each standard. For those interested in the full specifications, RSA's FTP site conveniently provides the PKCS series. If PKCS#1, v1.5 [6] is too dated (for example, multi-prime RSA), please see RFC 3447 for version 2.1 of the standard [21]. For the ITU-T X Series publications (including X.509 and X.690), visit the ITU website. PKCSPKCS is Public Key Cryptography Standard. The standard is maintained by RSA labs [4]. There are currently 10 standards, numbered 1 through 15 (PKCS#2 and PKCS#4 were merged into PKCS#1; PKCS#13 and PKCS#14 are listed as under development) [5]. Of the standards, PKCS#1: RSA Cryptography Standard and PKCS#8: Private-Key Information Syntax Standard are of interest. Using ASN.1, PKCS#1 defines the types X.509 CertificatesA public key certificate is a digitally signed statement from one entity, stating that the public key of another entity is authentic. A signed certificate binds an entity to a public key. The certificate allows us (the users) to confirm the identity of the owner of a public key. In addition, it allows us (the users) to confirm the authenticity of the public key. If the public key were tampered, the signature on the certificate would no longer be valid. The same applies if the entity's information was tampered or changed. One of the most common forms of a public key certificate is X.509. We regularly see X.509 certificates in use on the internet. In the case of web browsers and SSL, we usually do not know who we are trusting during a transaction. But we do trust a certification authority (CA), such as Verisign or Comodo, which has signed the other's certificate which is host to the public key. So, Verisign or Comodo attest to the identity of the person, group, or organization offering us their public key, by signing the organization's certificate. In addition, since the certification authority signed the public key certificate, we know the public key is authentic. We can trace the lineage of the X.509 certificate signers back to a CA which we trust. At the root, the authority signs its own certificate, which makes everything OK. Key SyntaxIn an effort to achieve interoperability, we use four formats provided in PKCS#1, PKCS# 8, and X.509. Using
PublicKeyInfoEven though X.509 is heavier than we need, it offers the first format: PublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
PublicKey BIT STRING }
According to X.509, AlgorithmIdentifier ::= SEQUENCE {
algorithm ALGORITHM.id,
parameters ALGORITHM.type OPTIONAL }
The syntax of Finally, the PrivateKeyInfoMoving to PKCS #8, we find the syntax for the second format, PrivateKeyInfo ::= SEQUENCE {
version Version,
privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
privateKey PrivateKey,
attributes [0] IMPLICIT Attributes OPTIONAL }
and Version ::= INTEGER
PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
PrivateKey ::= OCTET STRING
Recall that a public key is encoded as a bit string. EncryptedPrivateKeyInfoFinally, PKCS#8 defines a EncryptedPrivateKeyInfo ::= SEQUENCE {
encryptionAlgorithm EncryptionAlgorithmIdentifier,
encryptedData EncryptedData }
and EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
EncryptedData ::= OCTET STRING
RSAPublicKeyPKCS #1 defines the third syntax, RSAPublicKey ::= SEQUENCE {
modulus INTEGER,
publicExponent INTEGER }
RSAPrivateKeyReading PKCS #1 further, we find the final syntax, an RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER,
publicExponent INTEGER,
privateExponent INTEGER,
prime1 INTEGER,
prime2 INTEGER,
exponent1 INTEGER,
exponent2 INTEGER,
coefficient INTEGER }
and Version ::= INTEGER
Key FormatsThis section will present the programmatic structure of the keys, in an effort to bring the previous sections together in a meaningful manner. If the devil is in the details, it resides in finding the syntax (specification) for the RSA Public KeySEQUENCE // PublicKeyInfo
+- SEQUENCE // AlgorithmIdentifier
+- OID // 1.2.840.113549.1.1.1
+- NULL // Optional Parameters
+- BITSTRING // PublicKey
+- SEQUENCE // RSAPublicKey
+- INTEGER(N) // N
+- INTEGER(E) // E
RSA Private KeySEQUENCE // PrivateKeyInfo
+- INTEGER // Version - 0 (v1998)
+- SEQUENCE // AlgorithmIdentifier
+- OID // 1.2.840.113549.1.1.1
+- NULL // Optional Parameters
+- OCTETSTRING // PrivateKey
+- SEQUENCE // RSAPrivateKey
+- INTEGER(0) // Version - v1998(0)
+- INTEGER(N) // N
+- INTEGER(E) // E
+- INTEGER(D) // D
+- INTEGER(P) // P
+- INTEGER(Q) // Q
+- INTEGER(DP) // d mod p-1
+- INTEGER(DQ) // d mod q-1
+- INTEGER(Inv Q) // INV(q) mod p
DSA KeysIf we were interested in the Digital Signature Algorithm keys as defined in the Digital Signature Standard [16] and IEEE's P1363 [23], the keys would be as follows. DSA uses the Dss-Parms ::= SEQUENCE {
p INTEGER,
q INTEGER,
g INTEGER }
DSA Public KeySEQUENCE // PublicKeyInfo
+- SEQUENCE // AlgorithmIdentifier
+- OID // 1.2.840.10040.4.1
+- SEQUENCE // DSS-Params (Optional Parameters)
+- INTEGER(P) // P
+- INTEGER(Q) // Q
+- INTEGER(G) // G
+- BITSTRING // PublicKey
+- INTEGER(Y) // DSAPublicKey Y
DSA Private KeySEQUENCE // PrivateKeyInfo
+- INTEGER // Version
+- SEQUENCE // AlgorithmIdentifier
+- OID // 1.2.840.10040.4.1
+- SEQUENCE // DSS-Params (Optional Parameters)
+- INTEGER(P) // P
+- INTEGER(Q) // Q
+- INTEGER(G) // G
+- OCTETSTRING // PrivateKey
+- INTEGER(X) // DSAPrivateKey X
RSA CryptosystemRSA is the work of Ron Rivest, Adi Shamir, and Leonard Adleman. The system was developed in 1977, and patented by the Massachusetts Institute of Technology. The RSA patent expired in September of 2000, and was subsequently placed in Public Domain. Though Rivest, Shamir, and Adleman are generally credited with the discovery, Clifford Cocks (Chief Mathematician at GCHQ — the British equivalent of the NSA) described the system in 1973. However, Cocks did not publish since the work was considered classified, so the credit lay with Rivest, Shamir, and Adleman. Public and Private Key GenerationTo generate a key pair, we perform the following [10]:
When we compute d, we would use the Extended Euclidian Algorithm [10]. e is known as the encryption exponent, and d is the decryption exponent. The public key is (n, e); the private key is d. Public Key EncryptionTo encrypt a message, we perform the following [10]:
We would then send the cipher text c to the entity. Private Key DecryptionTo decrypt a message, we perform the following [10]:
RSAPrivateKey Syntax versus RSA Private KeysFrom Public and Private Key Generation, we know our keys consist of d, e, and n, yet
Generating, Saving, and Loading KeysBefore we can exchange keys over SneakerNet, we must generate a key pair. The keys we generate are temporary and can be deleted. At times, we will encounter the terms ephemeral or temporal, which are sometimes used to describe discardable keys. Best practice dictates that we use different keys for the purpose of key exchange and signing. This is known as Key Separation [11]. (Keep in mind we disregard the fact that we are saving private keys in Below, we will examine the code in a library-centric fashion, rather than each step (generating, saving, and loading) on a per library basis. It is easier to group all operations by library and discuss the library as a whole. In all cases, the file name for the key is formed as <type>.rsa.<library>.key. So, a public key generated using Crypto++ will be named public.rsa.cpp.key, while a Java private key will be named private.rsa.java.key. Crypto++Generating RSA keys in Crypto++ is straightforward once we know the classes which we need to use. In Crypto++, the private key is represented as The Key GenerationThe code below creates an RSA public and private key using a default parameter for the public exponent ( // Convenience
typedef InvertibleRSAFunction RSAPrivateKey;
typedef RSAFunction RSAPublicKey;
// PGP RandPool design
AutoSeededRandomPool prng;
// Private Key
RSAPrivateKey privateKey;
privateKey.Initialize( prng, 1024 /*, e=17*/)
// Public Key
RSAPublicKey publicKey( privateKey );
If we wanted to initialize the Initialize(Integer n, Integer e, Integer d)
Initialize(Integer n, Integer e, Integer d, Integer p,
Integer q, Integer dp, Integer dq, Integer u)
The RSAPublicKey publicKey( privateKey );
If we wanted to initialize the key with parameters Initialize(Integer n, Integer e)
Saving KeysRecall that the // Save as PKCS #8 (using ASN.1 DER Encoding )
privateKey.Save( FileSink("private.rsa.cpp.key") );
// Save as X.509 (using ASN.1 DER Encoding )
publicKey.Save( FileSink("public.rsa.cpp.key") );
Crypto++ uses a Unix pipeline paradigm, so we need a destination (the key is the source). This is the role of the
In Figure 2, we observe a few items. First, at file offset 0, we see a sequence. There are 628 content octets. Offset 0 marks the beginning of Offset 26 begins the Loading KeysLoading a key is equally trivial. Below, we load the keys generated in C#. We use a // Load as PKCS #8 (using ASN.1 BER Decoding )
privateKey.Load( FileSource( "private.rsa.cs.key", true ) );
// Load as X.509 (using ASN.1 BER Decoding )
publicKey.Load( FileSource( "public.rsa.cs.key", true ) );
If we inadvertently use publicKey.DEREncodePublicKey ( FileSink("public.rsa.cpp.key") );
we produce incorrect results. This is because only the ASN.1 integers are written (PKCS#1
In Figure 3, we note that two ASN.1 encoded integers ( For completeness, we will again call the wrong function when we load a key. We attempt to load an encoded publicKey.BERDecodePublicKey(FileSource( "public.rsa.cpp.key", true ));
JavaJava enjoys greater popularity with better documentation, so the following is presented for completeness. The Java Cryptography Extension (JCE) Reference Guide [13] answers most questions. Key GenerationTo generate our RSA keys, we perform the following. // Java Cryptography Provider
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
// Initialize and Generate
kpg.initialize(1024, new SecureRandom());
KeyPair keys = kpg.generateKeyPair();
// Retrieve keys
RSAPrivateKey privateKey = (RSAPrivateKey)keys.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey)keys.getPublic();
Saving KeysSaving a key in Java is only slightly more complicated since we have to construct a DataOutputStream dos = new DataOutputStream( new FileOutputStream("private.rsa.java.key"));
dos.write(privateKey.getEncoded);
dos.close();
Loading KeysLoading a key in Java is shown below. The // Retrieve bytes
FileInputStream fis = new FileInputStream("private.rsa.java.key")
DataInputStream dis = new DataInputStream(fis);
byte[] octets = new byte[(int) fis.length()];
dis.readFully(octets);
dis.close();
// Reconstruct Key
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(b);
KeyFactory factory = KeyFactory.getInstance("RSA");
RSAPrivateKey privateKey = (RSAPrivateKey)factory.generatePrivate(spec);
Above, the RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey)factory.generatePrivate(spec);
If we were reading a public key, the code above would be modified as follows. FileInputStream fis = new FileInputStream("public.rsa.java.key");
DataInputStream dis = new DataInputStream(fis);
...
X509EncodedKeySpec spec = new X509EncodedKeySpec(b);
KeyFactory factory = KeyFactory.getInstance("RSA");
RSAPublicKey publicKey = (RSAPublicKey)factory.generatePublic(spec);
C#C# is the most complicated of the examples, closely following its CAPICOM heritage. In addition, there are unwritten rules we must follow when importing keys. This section will attempt to examine the issues we are faced with when working within the confines of the CLR. RSA Key GenerationOur C# first task is to generate a key pair. We set the
We use the CspParameters csp = new CspParameters();
csp.KeyContainerName = "RSA Test (OK to Delete)";
csp.ProviderType = PROV_RSA_FULL; // 1
csp.KeyNumber = AT_KEYEXCHANGE; // 1
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024, csp);
RSAParameters privateKey = rsa.ExportParameters(true);
RSAParameters publicKey = rsa.ExportParameters(false);
Note that if we were using the Win32 API, we would retrieve a Saving RSA KeysNext, we need to serialize the keys. Since C# does not support our needs (unless we desire XML), we use the
Each method simply extracts the pertinent values from either AsnMessage privateEncoded = PrivateKeyToPKCS8(privateKey);
SaveEncodedKey("private.rsa.cs.key", privateEncoded.GetBytes());
AsnMessage publicEncoded = PublicKeyToX509(publicKey);
SaveEncodedKey("public.rsa.cs.key", publicEncoded.GetBytes());
The internal static void SaveEncodedKey(String filename, byte[] encoded)
{
using (BinaryWriter writer = new BinaryWriter(
new FileStream(filename, FileMode.Create, FileAccess.ReadWrite)))
{
writer.Write(encoded);
}
}
In Figure 5, we examine the resulting private key created using C#:
Because the CLR's native key format is either // Should be using PrivatKeyToPKCS8(...)
AsnMessage privateEncoded = PublicKeyToX509(privateKey);
SaveEncodedKey("private.rsa.cs.key", privateEncoded.GetBytes());
Loading RSA KeysNext, we load the private key using Java to verify encoding correctness. Notice the modulus (file offset 33) and public exponent (file offset 165) in Figure 5 match those displayed by our Java program in Figure 6.
Now, we will examine the case of loading a X.509 encoded AsnKeyParser keyParser = new AsnKeyParser("public.rsa.cs.key");
RSAParameters publicKey = keyParser.ParseRSAPublicKey();
Next, we construct a CspParameters csp = new CspParameters;
csp.KeyContainerName = "RSA Test (OK to Delete)";
csp.ProviderType = PROV_RSA_FULL; // 1
csp.KeyNumber = AT_KEYEXCHANGE; // 1
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
rsa.PersistKeyInCsp = false;
Unlike key generation, we are not confined to using only MSDN specified values for We are free to import either a public key or a private key, depending on our desired key usage. Below, we choose a public key which could be used for encryption or signing, and we import our parsed rsa.ImportParameters(publicKey);
Finally, we call rsa.Clear();
If we neglect to call
In addition, an event is written to the application event log stating '.NET Runtime version 2.0.50727.1433 — Fatal Execution Engine Error (79FFEE24) (80131506)'.
The reason is not readily apparent. In the sample, we call DSA Key GenerationNext we turn our attention to DSA. During key generation, we perform the same basic steps as with RSA. However, per MSDN, we specify a CspParameters csp = new CspParameters();
csp.KeyContainerName = "DSA Test (OK to Delete)";
csp.ProviderType = PROV_DSS_DH; // 13
csp.KeyNumber = AT_SIGNATURE; // 2
DSACryptoServiceProvider dsa = new DSACryptoServiceProvider(1024, csp);
After the key pair is created, we call the provider's DSAParameters privateKey = dsa.ExportParameters(true);
AsnMessage key = AsnKeyBuilder.PrivateKeyToPKCS8(privateKey);
Saving DSA KeysAs with RSA, we save an RSA key in PKCS #8 or X.509 format using either the AsnMessage privateEncoded = PrivateKeyToPKCS8(privateKey);
SaveEncodedKey("private.dsa.cs.key", privateEncoded.GetBytes());
AsnMessage publicEncoded = PublicKeyToX509(publicKey);
SaveEncodedKey("public.dsa.cs.key", publicEncoded.GetBytes());
Loading DSA KeysNext we move on to opening the container. In this case, the CspParameters csp = new CspParameters();
csp.ProviderType = PROV_DSS; // 3
csp.KeyNumber = AT_SIGNATURE; // 2
DSACryptoServiceProvider dsa = new DSACryptoServiceProvider(csp);
Above, we create a provider using AsnKeyParser keyParser = new AsnKeyParser("private.dsa.cs.key");
DSAParameters privateKey = keyParser.ParseDSAPrivateKey();
Common ErrorsNext, we will explore the most common reasons for failures during key import, which can lead to exceptions such as the ubiquitous 'Bad Data' exception shown in Figure 10.
First, we cannot specify a
Next is our key parser. When we exported the keys, we wrote four integers ( However, when we attempt to import the value into the servicer provider, we receive a 'Bad Data' exception. When we import a key, we must strip the 0x00 if present. RSA does not appear to suffer from this limitation, which makes us suspect that DSA fails internal validation because it considers the parameter size to be 1024+8 = 1032 bits. Our byte[] r = null;
if ( (values.Length > 1) && (0x00 == values[0]))
{
r = new byte[values.Length - 1];
Array.Copy(values, 1, r, 0, values.Length - 1);
}
Recall that we created the keys with
However, when we reconstruct the key which was serialized using PKCS#8 or X.509, the result will be similar to that shown in Figure 13.
Because PKCS#8 and X.509 do not serialize the validation parameters, we cannot use For completeness, RFC 2492 [24] (and ANSI X9.42 [25]) includes the ASN.1 syntax for the Diffie-Hellman key exchange, which is shown below. The syntax for the missing C# parameters is shown below: DomainParameters ::= SEQUENCE {
p INTEGER,
g INTEGER,
q INTEGER,
j INTEGER OPTIONAL,
validationParms ValidationParms OPTIONAL }
and ValidationParms ::= SEQUENCE {
seed BIT STRING,
pgenCounter INTEGER }
The OID is 1.2.840.10046.2.1. Whether this can be loaded into Java for DSA operations is questionable. 10046 is the ANSI-x942 arc in the OID tree, while 10040 (used for DSA) is the x9-57 arc. If we try to export the public key as a private key using // Load public key
AsnKeyParser keyParser = new AsnKeyParser(...);
RSAParameters publicKey = keyParser.ParseRSAPublicKey();
...
rsa.ImportParameters(publicKey);
// Export as private key
String xml = rsa.ToXmlString(true);
This is a valid key syntax according to RFC 3275, section 4.4.2.1: Parameters seed and pgenCounter are used in the DSA prime number generation algorithm specified in [DSS]. As such, they are optional, but must either both be present or both be absent. With our new found knowledge, we will try to break the provider. First, we write the DSA key out to a file in XML format. Next, we delete the
Next we copy the key and delete the seed. When we attempt to load bad.dsa.key.xml, we catch the exception "Input string does not contain a valid encoding of the 'DSA' 'Seed' parameter." We receive a similar exception when only the counter is deleted.
ASN.1ASN.1 is Abstract Syntax Notation One, which is a presentation layer protocol. It is a formal language for describing data and the properties of the data [3]. ASN.1 encoding rules are specified by the ITU in X.690 (X.208 was deprecated in 2002) [1]. For questions regarding ASN.1 and its use, visit the ASN.1 Consortium. Join their mailing list and then send questions to asn1@asn1.org. There are three types of encoding — BER, CER, and DER. Each offers varying degrees of freedom for encoding a value, with BER being the least restrictive and DER being the most restrictive. We usually find applications implement DER encoders and BER decoders. That is, an application attempts to write the most correct ASN.1 notation, while reading the least correct syntax. For example, if we wanted to encode the string "Crypto Interop", the single encoded string would satisfy BER, CER, and DER. However, if we used BER (the 'loosest'), we could also represent it with three strings that would be concatenated: "Crypto", " ", "Interop". This string concatenation is not a valid DER encoding. For more information on BER, CER, and DER encoding, please refer to X.690, sections 8, 9, and 10. For restrictions placed on BER encodings by CER and DER, please refer to X.690, section 11. There are exceptions to every rule, and this is no different. According to PKCS#8, "... [in] an RSA private key, ... the contents are a BER encoding of a value of type RSAPrivateKey" [8]. So, an encoder could use the less encumbered BER encoding. The fundamental ASN.1 unit is an Octet, which is an 8-bit byte. An ASN.1 encoding, composed of octets, usually has three parts: an Identifier, a Length, and Contents (except for type An identifier is further broken down: the 5 low order bits are a The length specifies the size of the content octets (the data values we are encoding). There are three ways to encode length: short (a definite form), long (a definite form), and indefinite form. We only use the first two forms (the third is equivalent to a runtime length encoding). In the short form, there is only one octet. The high bit of the octet is zero, and the remaining seven bits specify the number of octets that follow, which are content octets. In long form, the high bit is one. The remaining seven bits specify the number of octets which follow, that specify the length. Examples are shown in Table 2.
From above, we see we can encode a length of one in a few ways: '0x01', '0x81 0x01', and '0x82 0x00 0x01'. BER, being the loosest encoding, would allow all three. DER is most restrictive, and only allows '0x01' (from X.690, section 10.1: the definite form of length encoding shall be used, encoded in the minimum number of octets). We only use a subset of elements from the specification: INTEGER, BIT STRING, OCTET STRING, NULL, OBJECT IDENTIFIER, and SEQUENCE, which are explained below. INTEGERAn ASN.1 integer is assigned a tag number 2. It is a signed integer using a 2's compliment representation (the same as in most personal computers). Because it is signed, if we want to represent a positive integer which has its high bit set, we must prepend a 0x00 to the content octets. Examples are shown in Table 3. For more information, please refer to X.690, Section 8.3.
In cryptographic applications which exchange information, we are assured of the 2's compliment issue when we choose a key size which is a multiple of 8 (for example, 512 bits or 1024 bits). As a concrete example, suppose we want a 512 bit modulus. We need two random prime numbers, p and q, each of which is 256 bits in length. We ask the pseudo-random number generator for 256 bits. Prime numbers are odd, so we set the lowest order bit of the number (p or q) to 1. In order to assure the number is the required size (256 bits), we set the highest order bit to 1. We then test the number for primality. Because we set the highest order bit to 1, the ASN.1 integer would be interpreted as negative if the content octets were not modified. A peer system may or may not interpret the number as negative (though it should). This could cause our peer to reject the key. So we prepend 0x00 to the content octets before transferring the key material, to assure a positive number is received. We see an example of the prepending to assure a positive integer, in Figure 2. BIT STRINGAn ASN.1 bit string is assigned a tag number 3. An ASN.1 primitive bit string is an initial octet followed by zero, one, or more subsequent octets. The initial octet is a discard count, which specifies how many trailing bits are unused. The initial octet is 0x00 if no bits are discarded, and must be between 0 and 7 inclusive. It is used when the number of bits is not a multiple of 8. For example, to encode the bit string 1111 1111 1111, the string must be a multiple of 8, so our content octets would be 0x04 0xFF, 0xF0. 0xFF 0xF0 (1111 1111 1111 0000) is the bit string, while 0x04 specifies four bits are unused. There is also a constructed variant of a bit string, which does not use an initial octet (which we do not use). For more information, please refer to X.690, Section 8.6. OCTET STRINGAn ASN.1 octet string is assigned a tag number 4. An ASN.1 octet string is zero, one, or more octets. There are no special rules to remember as with integers and bit strings. For more information, please refer to X.690, Section 8.7. NULLAn ASN.1 null is assigned a tag number 5. Unlike other types, this object consists of only an identifier and length, which is 0. So, a null encoding is 0x05 0x00. OBJECT IDENTIFIERAn ASN.1 OID is assigned a tag number 6. ASN.1 performs special packing of the arcs of the tree, similar to length encoding. For our purposes, it is the ASN.1 encoded object identifier 1.2.840.113549.1.1. For more information, please refer to X.690, Section 8.19. SEQUENCEAn ASN.1 sequence is assigned a tag number 16. However, it is a constructed object, so the identifier we encounter is 0x30 (0x10 | 0x20). A sequence acts as an ordered container (this is in contrast to a set, which acts as an unordered container). A set can contain zero, one, or more elements. The content octets of a sequence are the octets of the types it contains. For example, to wrap an INTEGER (with value 4) in a SEQUENCE, our encoding would be 0x30 0x03 0x02 0x01 0x04. 0x02 0x01 0x04 is the integer 4, which becomes the content octets of the sequence. As another example, a sequence with no elements is encoded 0x30 00. For more information, please refer to X.690, Section 8.9. Acknowledgements
Checksums
References
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||