ADCS certificate serial number generation algorithms – a comprehensive guide
by Vadims Podāns
Hello S-1-1-0, @Crypt32 is again on a failboatboard with new blog post. Today I will share information about a little-known portion in configuration of Microsoft ADCS Certification Authority – serial number generation algorithm.
Every X.509 conforming CA generates a unique serial number for each issued certificate, which in fact is a long integer. As per RFC 5280 §4.1.2.2, serial numbers MUST be unique, not greater than 20 bytes long non-negative integer and at least 1 bit must be enabled in first byte. If first byte is zero, this byte is truncated unless it is the only byte. This means the first byte must be in range 0x0÷0x7f with a whole integer value up to 20 bytes. 0x00 0x00 0x01 serial number is truncated to 0x01. Various CA engines implement different serial number generation algorithms. Some allow generation of sequential serial numbers starting with 0x00, 0x01, 0x02, etc. (excluding 0x80÷0xff range for most significant byte), others may use cryptographically random serial numbers, GUID-based or custom conforming algorithm.
Microsoft ADCS Certification Authority implementation
I found only one Microsoft article that talks about this topic: Configure Serial Number Generation. However I found this article hard to understand and it misses some interesting details. In this post, I’m outlining all information I was able to get from different sources, including Microsoft in a more clear way.
and it is a REG_DWORD value by default. In certain cases its type is changed to REG_SZ (string). Default value is 0.
Option 1: HighSerial = REG_DWORD:0x0
Length
Description
4
GetTickCount() from system time
2
Zero-based CA certificate index
4
RequestID from database
Example: 15 fb a3 bb 00 00 00 00 00 02
Option 2: HighSerial = REG_DWORD:0x1÷0x7f
This option adds cryptographic random component inside serial number. Generated serial numbers are 19 (38 hex chars) bytes long:
Length
Description
1
Registry value of HighSerial
4
RequestID from database
8
CryptGenRandom()
2
Zero-based CA certificate index
4
RequestID from database
If registry value is in range 0x80÷0xfffffffe, only least-significant byte is accounted and is inserted as first byte of serial number. Sign bit is set to 0 (to force positive integer). If HighSerial is between 0x01-0x0f, random bits are inserted in most-significant 2-4 bits. See remarks section for more details.
Example:11 00 00 01 10 2e cf 52 a1 b2 24 d1 aa 00 00 00 00 00 02
Option 3: HighSerial = REG_DWORD:0xFFFFFFFF
Similar to default option where first component (GetTickCount()) is replaced with CryptGenRandom() function call. Generated serial numbers are 19 (38 hex chars) bytes long:
Length
Description
8
CryptGenRandom()
2
Zero-based CA certificate index
4
RequestID from database
When the CA attempts to generate its first serial number, 8 bytes of CryptGenRandom() data are produced and the registry is rewritten as a REG_SZ value to save the random data as a constant hex string. See Option 4 for more details.
Example:21 71 fa f5 14 f6 85 37 00 00 00 00 00 02
Option 4: HighSerial = REG_SZ:OCTET_STRING
This option uses REG_SZ value type instead of REG_DWORD. Generated serial numbers are from 7 to 19 bytes long depending on a number of octets in HighSerial registry value. Since value data is octet string it must have even number of hex digits.
Length
Description
1-13
HighSerial registry data
2
Zero-based CA certificate index
4
RequestID from database
If HighSerial registry value exceeds 13 octets, value is truncated to least-significant 13 bytes and truncated digits are ignored.
In all cases, the high byte of the serial number is manipulated to always have the sign bit clear, and to always have some bits set in the high nibble for compatibility with various PKI implementations with limited robustness. This means that:
if first byte of serial number is in range 0x80÷0xff, the most-significant bit is set to 0.
if first byte is 0x0 (all bits are zeroes), arbitrary bits are inserted to make it non-zero
at least one bit is set in most-significant 2-4 bits of first byte to avoid any kind of 0x0* pattern for the first byte
Remark 2
It is evident that ADCS uses database Request ID as a part of serial number. By reading last 4 bytes, you can estimate the size (issued certificate volume) of ADCS CA. When you delete rows from CA database, deleted request IDs are never re-used, thus inclusion of Request ID in serial number guarantees its uniqueness for a particular CA. When a CA reaches 2,147,483,647 issued certificates, the CA will die even if all previously issued certificates are deleted from its database. Request ID is never used twice.
Remark 3
I’ve received the same question several times on whether it is possible to configure truly sequential serial numbers (starting with 01 and up) with Microsoft ADCS. The answer is NO, it is not possible.
Related Resources
Blog
November 7, 2024
PKI Insights Recap – Is Your PKI Healthy? The Essential Guide to Comprehensive Assessments
PKI, PKI Insights
Blog
October 4, 2024
Announcing the October 2024 PKI Spotlight® Release
PKI, PKI Spotlight
Blog
August 16, 2024
To Revoke or Not to Revoke: Balancing Security with Performance and Operational Complexity
CA, Certificate Authority, Certificate Revocation List, CRL, OCSP, PKI, VPN
I was looking for an answer on your website how to address the challenge.
Auto-enrollment is enabled for a particular template of the certificate.
Everything works correctly, users are not able to “export private key” as on the template has not been selected the option “Allow private key to be exported”.
But… the user can request the certificate again manually, and can change the option to Allow private key to be exported.
How to prevent users to manually request the certificate and select the option Allow private key to be exported?
This can not be achieved. The private key exportable option is defined on the client and the CA has no way to enforce it. During AutoEnrollment Windows will use whatever is defined in the template. But when a user performs an enrollment manually, they can override anything that the CA isn’t able to see (they couldn’t change the keysize for instance as the CA sees the Public Key). The only way you can ever enforce this is to use Key Attestation and ensure the key is generated in a device that won’t allow the key to be exported. By the way, this checkbox/setting provides very little security. There are many programs out there that can be easily downloaded and export a key even if it is not marked as exportable.