Skip to content

Instantly share code, notes, and snippets.

@alterakey
Last active February 8, 2017 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alterakey/40d7d9148559bacf62858b5f143bb484 to your computer and use it in GitHub Desktop.
Save alterakey/40d7d9148559bacf62858b5f143bb484 to your computer and use it in GitHub Desktop.
An Android 6.0-compatible SHA1PRNG implementation
# An Android 6.0-compatible SHA1PRNG implementation
# Copyright 2017 Takahiro Yoshimura <altakey@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This implementation is based on:
# https://github.com/android/platform_development/blob/63cb77947e22ea5a311ac267e91edab2842bb8b7/samples/BrokenKeyDerivation/src/com/example/android/brokenkeyderivation/InsecureSHA1PRNGKeyDerivator.java
from ctypes import c_uint32, c_uint8
class SHA1PRNGKeyDerivator:
@staticmethod
def deriveKey(seed, keySizeInBytes):
derivator = SHA1PRNGKeyDerivator()
derivator.setSeed([x for x in seed])
key = [b'\0'] * keySizeInBytes
derivator.nextBytes(key)
t = b''.join((b'%c' % x) for x in key)
return t
_END_FLAGS = [0x80000000, 0x800000, 0x8000, 0x80]
_RIGHT1 = [0,40,48,56]
_RIGHT2 = [0,8,16,24]
_LEFT = [0,24,16,8]
_MASK = [0xFFFFFFFF, 0x00FFFFFF, 0x0000FFFF, 0x000000FF]
_HASHBYTES_TO_USE = 20
_FRAME_LENGTH = 16
_COUNTER_BASE = 0
_HASHCOPY_OFFSET = 0
_EXTRAFRAME_OFFSET = 5
_FRAME_OFFSET = 21
_MAX_BYTES = 48
_UNDEFINED = 0
_SET_SEED = 1
_NEXT_BYTES = 2
_H0 = 0x67452301
_H1 = 0xEFCDAB89
_H2 = 0x98BADCFE
_H3 = 0x10325476
_H4 = 0xC3D2E1F0
_BYTES_OFFSET = 81
_HASH_OFFSET = 82
_DIGEST_LENGTH = 20
def __init__(self):
self._seed = [0] * (self._HASH_OFFSET + self._EXTRAFRAME_OFFSET)
self._seed[self._HASH_OFFSET:self._HASH_OFFSET + 5] = [self._H0, self._H1, self._H2, self._H3, self._H4]
self._seedLength = 0
self._copies = [0] * (2 * self._FRAME_LENGTH + self._EXTRAFRAME_OFFSET)
self._nextBytes = [b'\0'] * self._DIGEST_LENGTH
self._nextBIndex = self._HASHBYTES_TO_USE
self._counter = self._COUNTER_BASE
self._state = self._UNDEFINED
@staticmethod
def _copy(from_, from_offset, to, to_offset, len_):
to[to_offset:to_offset + len_] = from_[from_offset:from_offset + len_]
def _updateSeed(self, bytes_):
self._updateHash(self._seed, bytes_, 0, len(bytes_) - 1)
self._seedLength += len(bytes_)
def setSeed(self, seed):
assert seed is not None, "seed == null"
if self._state == self._NEXT_BYTES:
self._copy(self._copies, self._HASHCOPY_OFFSET, self._seed, self._HASHCOPY_OFFSET, self._EXTRAFRAME_OFFSET)
self._state = self._SET_SEED
if seed:
self._updateSeed(seed)
def nextBytes(self, bytes_):
i, n = 0, 0
bits = 0
nextByteToReturn = 0
lastWord = 0
extraBytes = 7
assert bytes_ is not None, 'bytes == null'
lastWord = 0 if not self._seed[self._BYTES_OFFSET] else ((self._seed[self._BYTES_OFFSET] + extraBytes) >> 3 - 1)
assert self._state != self._UNDEFINED, 'No seed supplied!'
if self._state == self._SET_SEED:
self._copy(self._seed, self._HASH_OFFSET, self._copies, self._HASHCOPY_OFFSET, self._EXTRAFRAME_OFFSET)
for x in range(lastWord, self._FRAME_LENGTH + 2):
self._seed[x] = 0
bits = (self._seedLength << 3) + 64
if self._seed[self._BYTES_OFFSET] < self._MAX_BYTES:
self._seed[14] = c_uint32(bits >> 32).value
self._seed[15] = c_uint32(bits).value
else:
self._copies[self._EXTRAFRAME_OFFSET + 14] = c_uint32(bits >> 32).value
self._copies[self._EXTRAFRAME_OFFSET + 15] = c_uint32(bits).value
self._nextBIndex = self._HASHBYTES_TO_USE
self._state = self._NEXT_BYTES
if not len(bytes_):
return
nextByteToReturn = 0
n = min(self._HASHBYTES_TO_USE - self._nextBIndex, len(bytes_) - nextByteToReturn)
if n > 0:
self._copy(self._nextBytes, self._nextBIndex, bytes_, nextByteToReturn, n)
self._nextBIndex += n
nextByteToReturn += n
if nextByteToReturn >= len(bytes_):
return
n = self._seed[self._BYTES_OFFSET] & 0x03
while True:
if not n:
self._seed[lastWord] = c_uint32(self._counter >> 32).value
self._seed[lastWord + 1] = c_uint32(self._counter).value
self._seed[lastWord + 2] = c_uint32(self._END_FLAGS[0]).value
else:
self._seed[lastWord] |= c_uint32((self._counter >> self._RIGHT1[n]) & self._MASK[n]).value
self._seed[lastWord + 1] = c_uint32((self._counter >> self._RIGHT2[n])).value
self._seed[lastWord + 2] = c_uint32((self._counter << self._LEFT[n]) | self._END_FLAGS[n]).value
if self._seed[self._BYTES_OFFSET] > self._MAX_BYTES:
self._copies[self._EXTRAFRAME_OFFSET] = self._seed[self._FRAME_LENGTH]
self._copies[self._EXTRAFRAME_OFFSET + 1] = self._seed[self._FRAME_LENGTH + 1]
self._computeHash(self._seed)
if self._seed[self._BYTES_OFFSET] > self._MAX_BYTES:
self._copy(self._seed, 0, self._copies, self._FRAME_OFFSET, self._FRAME_LENGTH)
self._copy(self._copies, self._EXTRAFRAME_OFFSET, self._seed, 0, self._FRAME_LENGTH)
self._computeHash(self._seed)
self._copy(self._copies, self._FRAME_OFFSET, self._seed, 0, self._FRAME_LENGTH)
self._counter += 1
j = 0
for i in range(self._EXTRAFRAME_OFFSET):
k = self._seed[self._HASH_OFFSET + i]
self._nextBytes[j] = c_uint8(k >> 24).value
self._nextBytes[j + 1] = c_uint8(k >> 16).value
self._nextBytes[j + 2] = c_uint8(k >> 8).value
self._nextBytes[j + 3] = c_uint8(k).value
j += 4
self._nextBIndex = 0
j = min(self._HASHBYTES_TO_USE, len(bytes_) - nextByteToReturn)
if j > 0:
self._copy(self._nextBytes, 0, bytes_, nextByteToReturn, j)
nextByteToReturn += j
self._nextBIndex += j
if nextByteToReturn >= len(bytes_):
break
def _computeHash(self, arrW):
def _rol(t, b):
return c_uint32((t << b) | (t >> (32-b))).value
a = arrW[self._HASH_OFFSET ]
b = arrW[self._HASH_OFFSET +1]
c = arrW[self._HASH_OFFSET +2]
d = arrW[self._HASH_OFFSET +3]
e = arrW[self._HASH_OFFSET +4]
temp = 0
for t in range(16, 80):
temp = arrW[t-3] ^ arrW[t-8] ^ arrW[t-14] ^ arrW[t-16]
arrW[t] = _rol(temp, 1)
for t in range(0, 20):
temp = c_uint32(_rol(a, 5) + ((b & c) | ((~b) & d)) + (e + arrW[t] + 0x5A827999)).value
e = d
d = c
c = _rol(b, 30)
b = a
a = temp
for t in range(20, 40):
temp = c_uint32(_rol(a, 5) + (b ^ c ^ d) + (e + arrW[t] + 0x6ED9EBA1)).value
e = d
d = c
c = _rol(b, 30)
b = a
a = temp
for t in range(40, 60):
temp = c_uint32(_rol(a, 5) + ((b & c) | (b & d) | (c & d)) + (e + arrW[t] + 0x8F1BBCDC)).value
e = d
d = c
c = _rol(b, 30)
b = a
a = temp
for t in range(60, 80):
temp = c_uint32(_rol(a, 5) + (b ^ c ^ d) + (e + arrW[t] + 0xCA62C1D6)).value
e = d
d = c
c = _rol(b, 30)
b = a
a = temp
arrW[self._HASH_OFFSET+0] = c_uint32(arrW[self._HASH_OFFSET+0] + a).value
arrW[self._HASH_OFFSET+1] = c_uint32(arrW[self._HASH_OFFSET+1] + b).value
arrW[self._HASH_OFFSET+2] = c_uint32(arrW[self._HASH_OFFSET+2] + c).value
arrW[self._HASH_OFFSET+3] = c_uint32(arrW[self._HASH_OFFSET+3] + d).value
arrW[self._HASH_OFFSET+4] = c_uint32(arrW[self._HASH_OFFSET+4] + e).value
def _updateHash(self, intArray, byteInput, fromByte, toByte):
index = intArray[self._BYTES_OFFSET]
i = fromByte
maxWord = 0
nBytes = 0
wordIndex = index >>2
byteIndex = index & 0x03
intArray[self._BYTES_OFFSET] = (index + toByte - fromByte + 1) & 0o077
if byteIndex:
while i <= toByte and byteIndex < 4:
intArray[wordIndex] |= c_uint8(byteInput[i]).value << ((3 - byteIndex)<<3)
byteIndex += 1
i += 1
if byteIndex == 4:
wordIndex += 1
if wordIndex == 16:
self._computeHash(intArray)
wordIndex = 0
if i > toByte:
return
maxWord = (toByte - i + 1) >> 2
for k in range(0, maxWord):
intArray[wordIndex] = (c_uint8(byteInput[i]).value <<24) | (c_uint8(byteInput[i+1]).value <<16) | (c_uint8(byteInput[i+2]).value <<8) | c_uint8(byteInput[i+3]).value
i += 4
wordIndex += 1
if wordIndex < 16:
continue
self._computeHash(intArray)
wordIndex = 0
nBytes = toByte - i +1
if nBytes:
w = c_uint8(byteInput[i]).value <<24
if nBytes != 1:
w |= c_uint8(byteInput[i+1]).value <<16
if nBytes != 2:
w |= c_uint8(byteInput[i+2]).value <<8
intArray[wordIndex] = w
return
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment