How to programmatically calculate Chrome extension ID?

I was only able to find a related article with a Ruby fragment, and it's only available in the IA: http://web.archive.org/web/20120606044635/http://supercollider.dk/2010/01/calculating-chrome-extension-id-from-your-private-key-233

Important to know:

  1. This depends on a DER-encoded public key (raw binary), not a PEM-encoded key (nice ASCII generated by base64-encoding the DER key).
  2. The extension-IDs are base-16, but are encoded using [a-p] (called "mpdecimal"), rather than [0-9a-f].

Using a PEM-encoded public key, follow the following steps:

  1. If your PEM-formatted public-key still has the header and footer and is split into multiple lines, reformat it by hand so that you have a single string of characters that excludes the header and footer, and runs together such that every line of the key wraps to the next.
  2. Base64-decode the public key to render a DER-formatted public-key.
  3. Generate a SHA256 hex-digest of the DER-formatted key.
  4. Take the first 32-bytes of the hash. You will not need the rest.
  5. For each character, convert it to base-10, and add the ASCII code for 'a'.

The following is a Python routine to do this:

import hashlib
from base64 import b64decode

def build_id(pub_key_pem):
    pub_key_der = b64decode(pub_key_pem)
    sha = hashlib.sha256(pub_key_der).hexdigest()
    prefix = sha[:32]

    reencoded = ""
    ord_a = ord('a')
    for old_char in prefix:
        code = int(old_char, 16)
        new_char = chr(ord_a + code)

        reencoded += new_char

    return reencoded

def main():
    pub_key = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjvF5pjuK8gRaw/2LoRYi37QqRd48B/FeO9yFtT6ueY84z/u0NrJ/xbPFc9OCGBi8RKIblVvcbY0ySGqdmp0QsUr/oXN0b06GL4iB8rMhlO082HhMzrClV8OKRJ+eJNhNBl8viwmtJs3MN0x9ljA4HQLaAPBA9a14IUKLjP0pWuwIDAQAB'

    id_ = build_id(pub_key)
    print(id_)

if __name__ == '__main__':
    main()

You're more than welcome to test this against an existing extension and its ID. To retrieve its PEM-formatted public-key:

  1. Go into the list of your existing extensions in Chrome. Grab the extension-ID of one.
  2. Find the directory where the extension is hosted. On my Windows 7 box, it is: C:\Users<username>\AppData\Local\Google\Chrome\User Data\Default\Extensions<extension ID>
  3. Grab the public-key from the manifest.json file under "key". Since the key is already ready to be base64-decoded, you can skip step (1) of the process.

The public-key in the example is from the "Chrome Reader" extension. Its extension ID is "lojpenhmoajbiciapkjkiekmobleogjc".

See also:

  1. Google Chrome - Alphanumeric hashes to identify extensions
  2. http://blog.roomanna.com/12-14-2010/getting-an-extensions-id

Starting with Chrome 64, Chrome changed the package format for extensions to the CRX₃ file format, which supports multiple signatures and explicitly declares its CRX ID. Extracting the CRX ID from a CRX₃ file requires parsing a protocol buffer.

Here is a small python script for extracting the ID from a CRX₃ file. This solution should only be used with trusted CRX₃ files or in contexts where security is not a concern: unlike CRX₂, the package format does not restrict what CRX ID a CRX₃ file declares. (In practice, consumers of the file (i.e. Chrome) will place restrictions upon it, such as requiring the file to be signed with at least one key that hashes to the declared CRX ID).

import binascii
import string
import struct
import sys

def decode(proto, data):
    index = 0
    length = len(data)
    msg = dict()
    while index < length:
        item = 128
        key = 0
        left = 0
        while item & 128:
            item = data[index]
            index += 1
            value = (item & 127) << left
            key += value
            left += 7
        field = key >> 3
        wire = key & 7
        if wire == 0:
            item = 128
            num = 0
            left = 0
            while item & 128:
                item = data[index]
                index += 1
                value = (item & 127) << left
                num += value
                left += 7
            continue
        elif wire == 1:
            index += 8
            continue
        elif wire == 2:
            item = 128
            _length = 0
            left = 0
            while item & 128:
                item = data[index]
                index += 1
                value = (item & 127) << left
                _length += value
                left += 7
            last = index
            index += _length
            item = data[last:index]
            if field not in proto:
                continue
            msg[proto[field]] = item
            continue
        elif wire == 5:
            index += 4
            continue
        raise ValueError(
            'invalid wire type: {wire}'.format(wire=wire)
        )
    return msg

def get_extension_id(crx_file):
    with open(crx_file, 'rb') as f:
      f.read(8); # 'Cr24\3\0\0\0'
      data = f.read(struct.unpack('<I', f.read(4))[0])
    crx3 = decode(
        {10000: "signed_header_data"},
        [ord(d) for d in data])
    signed_header = decode(
        {1: "crx_id"},
        crx3['signed_header_data'])
    return string.translate(
        binascii.hexlify(bytearray(signed_header['crx_id'])),
        string.maketrans('0123456789abcdef', string.ascii_lowercase[:16]))

def main():
    if len(sys.argv) != 2:
      print 'usage: %s crx_file' % sys.argv[0]
    else:
      print get_extension_id(sys.argv[1])

if __name__ == "__main__":
    main()

(Thanks to https://github.com/thelinuxkid/python-protolite for the protobuf parser skeleton.)


A nice and simple way to get the public key from the .crx file using python, since chrome only generates the private .pem key for you. The public key is actually stored in the .crx file.

This is based on the format of the .crx file found here http://developer.chrome.com/extensions/crx.html

import struct
import hashlib
import string

def get_pub_key_from_crx(crx_file):
    with open(crx_file, 'rb') as f:
        data = f.read()
    header = struct.unpack('<4sIII', data[:16])
    pubkey = struct.unpack('<%ds' % header[2], data[16:16+header[2]])[0]
    return pubkey

def get_extension_id(crx_file):
    pubkey = get_pub_key_from_crx(crx_file)
    digest = hashlib.sha256(pubkey).hexdigest()

    trans = string.maketrans('0123456789abcdef', string.ascii_lowercase[:16])
    return string.translate(digest[:32], trans)

if __name__ == '__main__':
    import sys
    if len(sys.argv) != 2:
        print 'usage: %s crx_file' % sys.argv[0]

    print get_extension_id(sys.argv[1])

Although this isn't possible to do "bypassing interaction with the browser", because you still need to generate the .crx file with a command like

chrome.exe --pack-extension=my_extension --pack-extension-key=my_extension.pem