Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to programmatically calculate Chrome extension ID?

I'm building an automated process to produce extensions. Is there a code example of calculating the extension-ID directly and entirely bypassing interaction with the browser?

(I'm answering my own question, below.)

like image 618
Dustin Oprea Avatar asked Jun 07 '13 21:06

Dustin Oprea


2 Answers

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
like image 183
Dustin Oprea Avatar answered Sep 19 '22 20:09

Dustin Oprea


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.)

like image 30
Minimize PII Avatar answered Sep 20 '22 20:09

Minimize PII