Skip to content

Instantly share code, notes, and snippets.

Created May 16, 2013 08:34
Show Gist options
  • Save anonymous/5590276 to your computer and use it in GitHub Desktop.
Save anonymous/5590276 to your computer and use it in GitHub Desktop.
Python JKS reader (not yet working)
'''
JKS file format decoder.
Use in conjunction with PyOpenSSL to translate to PEM, or load private key and certs
directly into openssl structs and wrap sockets.
See http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/security/provider/JavaKeyStore.java#JavaKeyStore.engineLoad%28java.io.InputStream%2Cchar%5B%5D%29
'''
import struct
import hashlib
import collections
from Crypto.PublicKey import RSA
class KeyStore(object):
def __init__(self, private_key, certs):
self.private_key = private_key
self.certs = certs
@classmethod
def load(cls, filename, password):
with open(filename) as file:
return cls.loads(file.read(), password)
@classmethod
def loads(cls, data, password):
if data[:4] != MAGIC_NUMBER:
raise ValueError('not keystore data (magic number wrong)')
version = b4.unpack_from(data, 4)[0]
if version != 2:
raise ValueError('only jks format v2 supported (got v'+repr(version)+')')
entry_count = b4.unpack_from(data, 8)[0]
pos = 12
private_key = None
certs = []
for i in range(entry_count):
tag = b4.unpack_from(data, pos)[0]
pos += 4
alias, pos = _read_utf(data, pos)
timestamp = b8.unpack_from(data, pos)[0]
pos += 8
if tag == 1: #private key
pkcs8_data, pos = _read_data(data, pos)
chain_len = b4.unpack_from(data, pos)[0]
pos += 4
cert_chain = []
for j in range(chain_len):
cert_type, pos = _read_utf(data, pos)
cert_data, pos = _read_data(data, pos)
cert_chain.append((cert_type, cert_data))
#TODO: better decoding
private_key = PrivateKey(
alias, timestamp, RSA.importKey(pkcs8_data, password), cert_chain)
elif tag == 2: #cert
cert_type, pos = _read_utf(data, pos)
cert_data, pos = _read_data(data, pos)
certs.append(Cert(alias, timestamp, cert_type, cert_data))
if hashlib.sha1(data[:pos] + password).digest() != data[pos:]:
raise ValueError("Hash mismatch; incorrect password or data corrupted")
return cls(private_key, certs)
Cert = collections.namedtuple("Cert", "alias timestamp type cert")
PrivateKey = collections.namedtuple("PrivateKey", "alias timestamp pkey cert_chain")
b8 = struct.Struct('>Q')
b4 = struct.Struct('>L')
b2 = struct.Struct('>H')
MAGIC_NUMBER = b4.pack(0xFEEDFEED)
VERSION = b4.pack(2)
def _read_utf(data, pos):
size = b2.unpack_from(data, pos)[0]
pos += 2
return unicode(data[pos:pos+size], 'utf-8'), pos+size
def _read_data(data, pos):
size = b4.unpack_from(data, pos)[0]
pos += 4
return data[pos:pos+size], pos+size
def test():
try:
KeyStore.loads(
'\xfe\xed\xfe\xed\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\x00'+\
'\x0cserver-alias\x00\x00\x01>\xacav\xd6\x00\x00\x05\x020\x82\x04\xfe0'+\
'\x0e\x06\n+\x06\x01\x04\x01*\x02\x11\x01\x01\x05\x00\x04\x82\x04\xeaEV'+\
'\xc7\xf4O$\xd3\xaa\xa7\xabo\x11\xa0"6\xd2b\x91ms/\xaf*\x919K\xc8\xbe('+\
'\xad\x85\x8e\x9b\xfeT\xe2\x12o!U\x83\xbd\xc3p\xfc56\xbbX\xe0\xb4\xc38'+\
'\x9f\xfbZ1\xdd\xb7\xd0\xbf\xec\xce\xe7\x15\x14\xfeP&\x16\xca\x0b%\xd8)'+\
'\xcb\xda\xd6x7\xc6$y\xe9\x95\xb1o\xac\x8f\x0f\x8dat\xbe\x8bW_\xddi\xb0~'+\
'\xa8\xca\xeb\xe0t\ryg\xee}\xc4\x90\x0c!\x82b\xa20\xe2k4F\xe4', 'changeit')
except Exception:
import traceback
traceback.print_exc()
import pdb
pdb.post_mortem()
For some reason the length is anomalously long: 1000-ish when it should be 100-ish
Here is the first bytes of the JKS file broken down into fields:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8901234567890 1 2 34 56 7 8 9 0 1
\xfe\xed\xfe\xed\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\x00\x0cserver-alias\x00\x00\x01>\xacav\xd6\x00\x00\x05\x02
MAGIC BYTES |VERSION (2) |ENTRIES(1) |TAG(1=privkey) | len(12) + alias | TIMESTAMP (8 bytes) |KEYLEN (=1282)
Clearly, the Key Length is way too long (the entire jks file is only 190 bytes!)
Not sure what is happening here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment