Return to Snippet

Revision: 58543
at July 22, 2012 15:40 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of proposed improvements over naive ARC4:
#
#   -   Uses KSA from VMPC minus an IV.
#   -   Uses 2 state spaces (RC4A). Splits the key and nonce to produce a key and nonce for each
#       state space. Each subkey and subnonce is XOR'd together to produce a new subkey.
#   -   Takes a nonce alongside the key. The key and nonce must be random and of even, equal
#       length, with 512 bytes per key/nonce suggested. **TODO**: They should be hashed, but they
#       are not currently, until I select a hash function with an appropriately sized output, which
#       won't limit the keyspace available to IARC4.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192).
#   -   A KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.
#       Passing the `expires` option to IARC4 will alter this limit.
#
#   This code should not be considered secure. It has not been cryptanalyzed and should not be used
#   in production. This code is strictly experimental.

from bitstring import BitArray
from Crypto.Random import random

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def random_bytes(n_bytes=512):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   VMPC state preparation without vector
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for n in range(state_size*3):
        i = n % state_size
        j = state[(((j + state[i]) % state_size) + key[i % key_size]) % state_size]

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Generic error (e.g., key and nonce of different lengths)
class KeyError(Exception): pass

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(KeyError): pass
                                          
#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256, expires=8447):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == expires): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering.
def IARC4(key, nonce, drop=8192, expires=255):
    key_length = len(key)

    #   Keys and nonces must be of even, equal lengths, 512 bytes apiece is recommended.
    if ((key_length != len(nonce)) or ((key_length % 2) != 0)):
        raise KeyError

    #   Split key and nonce in half to gain key_a, key_b, nonce_a, and nonce_b
    key_split   = key_length >> 1
    key_a       = BitArray(bytes=key[:key_split])
    key_b       = BitArray(bytes=key[key_split:])
    nonce_a     = BitArray(bytes=nonce[:key_split])
    nonce_b     = BitArray(bytes=nonce[key_split:])

    #   XOR keys and nonces.
    key_a = (key_a ^ nonce_a).bytes
    key_b = (key_b ^ nonce_b).bytes

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b, expires=(drop + expires))

    #   Discard first 4096 iterations of each state space
    for dropped in range(drop): next(keystream)

    #   Return the prepared PRNG to begin accepting input
    def _IARC4(text):
        #   Combine the keystream and the text stream
        for key_byte, text_byte in zip(keystream, text):
            yield key_byte ^ text_byte

    return _IARC4

#   Test Vector for IARC4
#
#   nonce:      b'\x01' + bytes(255) + b'\x02' + bytes(255)
#   key:        b'key_a' + bytes(251) + b'key_b' + bytes(251)
#   plaintext:  b'Plaintext'
#   ciphertext: b'\xb1\xc5\x8d)\x90\xf9WN\x94'

if (__name__ == "__main__"):
    nonce           = b'\x01' + bytes(255) + b'\x02' + bytes(255)
    key             = b'key_a' + bytes(251) + b'key_b' + bytes(251)
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encrypter       = IARC4(key, nonce)
    encryption      = encrypter(plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decrypter       = IARC4(key, nonce)
    decryption      = decrypter(ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" %
    (nonce, key, plaintext, ciphertext))

Revision: 58542
at July 22, 2012 15:15 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of proposed improvements over naive ARC4:
#
#   -   Uses KSA from VMPC minus an IV.
#   -   Uses 2 state spaces (RC4A). Splits the key and nonce to produce a key and nonce for each
#       state space. Each subkey and subnonce is XOR'd together to produce a new subkey.
#   -   Takes a nonce alongside the key. The key and nonce must be random and of even, equal
#       length, with 512 bytes per key/nonce suggested.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192).
#   -   A KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.
#       Passing the `expires` option to IARC4 will alter this limit.
#
#   This code should not be considered secure. It has not been cryptanalyzed and should not be used
#   in production. This code is strictly experimental.

from bitstring import BitArray
from Crypto.Random import random

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def random_bytes(n_bytes=512):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   VMPC state preparation without vector
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for n in range(state_size*3):
        i = n % state_size
        j = state[(((j + state[i]) % state_size) + key[i % key_size]) % state_size]

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Generic error (e.g., key and nonce of different lengths)
class KeyError(Exception): pass

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(KeyError): pass
                                          
#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256, expires=8447):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == expires): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering.
def IARC4(key, nonce, drop=8192, expires=255):
    key_length = len(key)

    #   Keys and nonces must be of even, equal lengths, 512 bytes apiece is recommended.
    if ((key_length != len(nonce)) or ((key_length % 2) != 0)):
        raise KeyError

    #   Split key and nonce in half to gain key_a, key_b, nonce_a, and nonce_b
    key_split   = int(key_length / 2)
    key_a       = BitArray(bytes=key[:key_split])
    key_b       = BitArray(bytes=key[key_split:])
    nonce_a     = BitArray(bytes=nonce[:key_split])
    nonce_b     = BitArray(bytes=nonce[key_split:])

    #   XOR keys and nonces.
    key_a = (key_a ^ nonce_a).bytes
    key_b = (key_b ^ nonce_b).bytes

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b, expires=(drop + expires))

    #   Discard first 4096 iterations of each state space
    for dropped in range(drop): next(keystream)

    #   Return the prepared PRNG to begin accepting input
    def _IARC4(text):
        #   Combine the keystream and the text stream
        for key_byte, text_byte in zip(keystream, text):
            yield key_byte ^ text_byte

    return _IARC4

#   Test Vector for IARC4
#
#   nonce:      b'\x01' + bytes(255) + b'\x02' + bytes(255)
#   key:        b'key_a' + bytes(251) + b'key_b' + bytes(251)
#   plaintext:  b'Plaintext'
#   ciphertext: b'\xb1\xc5\x8d)\x90\xf9WN\x94'

if (__name__ == "__main__"):
    nonce           = b'\x01' + bytes(255) + b'\x02' + bytes(255)
    key             = b'key_a' + bytes(251) + b'key_b' + bytes(251)
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encrypter       = IARC4(key, nonce)
    encryption      = encrypter(plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decrypter       = IARC4(key, nonce)
    decryption      = decrypter(ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" %
    (nonce, key, plaintext, ciphertext))

Revision: 58541
at July 22, 2012 15:14 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of proposed improvements over naive ARC4:
#
#   -   Uses KSA from VMPC minus an IV.
#   -   Uses 2 state spaces (RC4A). Splits the key and nonce to produce a key and nonce for each
#       state space. Each subkey and subnonce is XOR'd together to produce a new subkey. They should be hashed, but they are not currently, until I select a hash function with an appropriately sized output, which won't limit the keyspace available to IARC4.
#   -   Takes a nonce alongside the key. The key and nonce must be random and of even, equal
#       length, with 512 bytes per key/nonce suggested.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192).
#   -   A KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.
#       Passing the `expires` option to IARC4 will alter this limit.
#
#   This code should not be considered secure. It has not been cryptanalyzed and should not be used
#   in production. This code is strictly experimental.

from bitstring import BitArray
from Crypto.Random import random

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def random_bytes(n_bytes=512):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   VMPC state preparation without vector
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for n in range(state_size*3):
        i = n % state_size
        j = state[(((j + state[i]) % state_size) + key[i % key_size]) % state_size]

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Generic error (e.g., key and nonce of different lengths)
class KeyError(Exception): pass

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(KeyError): pass
                                          
#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256, expires=8447):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == expires): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering.
def IARC4(key, nonce, drop=8192, expires=255):
    key_length = len(key)

    #   Keys and nonces must be of even, equal lengths, 512 bytes apiece is recommended.
    if ((key_length != len(nonce)) or ((key_length % 2) != 0)):
        raise KeyError

    #   Split key and nonce in half to gain key_a, key_b, nonce_a, and nonce_b
    key_split   = int(key_length / 2)
    key_a       = BitArray(bytes=key[:key_split])
    key_b       = BitArray(bytes=key[key_split:])
    nonce_a     = BitArray(bytes=nonce[:key_split])
    nonce_b     = BitArray(bytes=nonce[key_split:])

    #   XOR keys and nonces.
    key_a = (key_a ^ nonce_a).bytes
    key_b = (key_b ^ nonce_b).bytes

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b, expires=(drop + expires))

    #   Discard first 4096 iterations of each state space
    for dropped in range(drop): next(keystream)

    #   Return the prepared PRNG to begin accepting input
    def _IARC4(text):
        #   Combine the keystream and the text stream
        for key_byte, text_byte in zip(keystream, text):
            yield key_byte ^ text_byte

    return _IARC4

#   Test Vector for IARC4
#
#   nonce:      b'\x01' + bytes(255) + b'\x02' + bytes(255)
#   key:        b'key_a' + bytes(251) + b'key_b' + bytes(251)
#   plaintext:  b'Plaintext'
#   ciphertext: b'\xb1\xc5\x8d)\x90\xf9WN\x94'

if (__name__ == "__main__"):
    nonce           = b'\x01' + bytes(255) + b'\x02' + bytes(255)
    key             = b'key_a' + bytes(251) + b'key_b' + bytes(251)
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encrypter       = IARC4(key, nonce)
    encryption      = encrypter(plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decrypter       = IARC4(key, nonce)
    decryption      = decrypter(ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" %
    (nonce, key, plaintext, ciphertext))

Revision: 58540
at July 21, 2012 09:12 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of proposed improvements over naive ARC4:
#
#   -   Uses KSA from VMPC minus an IV.
#   -   Uses 2 state spaces (RC4A). Splits the key and nonce to produce a key and nonce for each
#       state space. Each subkey and subnonce is XOR'd together to produce a new subkey.
#   -   Takes a nonce alongside the key. The key and nonce must be random and of even, equal
#       length, with 512 bytes per key/nonce suggested.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192).
#   -   A KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.
#       Passing the `expires` option to IARC4 will alter this limit.
#
#   This code should not be considered secure. It has not been cryptanalyzed and should not be used
#   in production. This code is strictly experimental.

from bitstring import BitArray
from Crypto.Random import random

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def random_bytes(n_bytes=512):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   VMPC state preparation without vector
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for n in range(state_size*3):
        i = n % state_size
        j = state[(((j + state[i]) % state_size) + key[i % key_size]) % state_size]

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Generic error (e.g., key and nonce of different lengths)
class KeyError(Exception): pass

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(KeyError): pass
                                          
#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256, expires=8447):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == expires): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering.
def IARC4(key, nonce, drop=8192, expires=255):
    key_length = len(key)

    #   Keys and nonces must be of even, equal lengths, 512 bytes apiece is recommended.
    if ((key_length != len(nonce)) or ((key_length % 2) != 0)):
        raise KeyError

    #   Split key and nonce in half to gain key_a, key_b, nonce_a, and nonce_b
    key_split   = int(key_length / 2)
    key_a       = BitArray(bytes=key[:key_split])
    key_b       = BitArray(bytes=key[key_split:])
    nonce_a     = BitArray(bytes=nonce[:key_split])
    nonce_b     = BitArray(bytes=nonce[key_split:])

    #   XOR keys and nonces.
    key_a = (key_a ^ nonce_a).bytes
    key_b = (key_b ^ nonce_b).bytes

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b, expires=(drop + expires))

    #   Discard first 4096 iterations of each state space
    for dropped in range(drop): next(keystream)

    #   Return the prepared PRNG to begin accepting input
    def _IARC4(text):
        #   Combine the keystream and the text stream
        for key_byte, text_byte in zip(keystream, text):
            yield key_byte ^ text_byte

    return _IARC4

#   Test Vector for IARC4
#
#   nonce:      b'\x01' + bytes(255) + b'\x02' + bytes(255)
#   key:        b'key_a' + bytes(251) + b'key_b' + bytes(251)
#   plaintext:  b'Plaintext'
#   ciphertext: b'\xb1\xc5\x8d)\x90\xf9WN\x94'

if (__name__ == "__main__"):
    nonce           = b'\x01' + bytes(255) + b'\x02' + bytes(255)
    key             = b'key_a' + bytes(251) + b'key_b' + bytes(251)
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encrypter       = IARC4(key, nonce)
    encryption      = encrypter(plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decrypter       = IARC4(key, nonce)
    decryption      = decrypter(ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" %
    (nonce, key, plaintext, ciphertext))

Revision: 58539
at July 21, 2012 09:08 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of proposed improvements over naive ARC4:
#
#   -   Uses KSA from VMPC minus an IV.
#   -   Uses 2 state spaces (RC4A). Splits the key and nonce to produce a key and nonce for each
#       state space. Each subkey and subnonce is XOR'd together to produce a new subkey.
#   -   Takes a nonce alongside the key. The key and nonce must be random and of even, equal
#       length, with 512 bytes per key/nonce suggested.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192).
#   -   A KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.
#       Passing the `expires` option to IARC4 will alter this limit.
#
#   This code should not be considered secure. It has not been cryptanalyzed and should not be used
#   in production. This code is strictly experimental.

from bitstring import BitArray
from Crypto.Random import random

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def random_bytes(n_bytes=512):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   VMPC state preparation without vector
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for n in range(state_size*3):
        i = n % state_size
        j = state[(((j + state[i]) % state_size) + key[i % key_size]) % state_size]

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state


#   Generic error (e.g., key and nonce of different lengths)
class KeyError(Exception): pass

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(KeyError): pass


#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256, expires=8447):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == expires): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering.
def IARC4(key, nonce, drop=8192, expires=255):
    key_length = len(key)

    #   Keys and nonces must be of even, equal lengths, 512 bytes apiece is recommended.
    if ((key_length != len(nonce)) or ((key_length % 2) != 0)):
        raise KeyError

    #   Split key and nonce in half to gain key_a, key_b, nonce_a, and nonce_b
    key_split   = int(key_length / 2)
    key_a       = BitArray(bytes=key[:key_split])
    key_b       = BitArray(bytes=key[key_split:])
    nonce_a     = BitArray(bytes=nonce[:key_split])
    nonce_b     = BitArray(bytes=nonce[key_split:])

    #   XOR keys and nonces.
    key_a = (key_a ^ nonce_a).bytes
    key_b = (key_b ^ nonce_b).bytes

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b, expires=(drop + expires))

    #   Discard first 4096 iterations of each state space
    for dropped in range(drop): next(keystream)

    #   Return the prepared PRNG to begin accepting input
    def _IARC4(text):
        #   Combine the keystream and the text stream
        for key_byte, text_byte in zip(keystream, text):
            yield key_byte ^ text_byte

    return _IARC4

#   Test Vector for IARC4
#
#   nonce:      b'\x01' + bytes(255) + b'\x02' + bytes(255)
#   key:        b'key_a' + bytes(251) + b'key_b' + bytes(251)
#   plaintext:  b'Plaintext'
#   ciphertext: b'\xb1\xc5\x8d)\x90\xf9WN\x94'

if (__name__ == "__main__"):
    nonce           = b'\x01' + bytes(255) + b'\x02' + bytes(255)
    key             = b'key_a' + bytes(251) + b'key_b' + bytes(251)
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encrypter       = IARC4(key, nonce)
    encryption      = encrypter(plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decrypter       = IARC4(key, nonce)
    decryption      = decrypter(ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" %
    (nonce, key, plaintext, ciphertext))

Revision: 58538
at July 21, 2012 09:05 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of proposed improvements over naive ARC4:
#
#   -   Uses KSA from VMPC minus an IV.
#   -   Uses 2 state spaces (RC4A). Splits the key and nonce to produce a key and nonce for each
#       state space. Each subkey and subnonce is XOR'd together to produce a new subkey.
#   -   Takes a nonce alongside the key. The key and nonce must be random and of even, equal
#       length, with 512 bytes per key/nonce suggested.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192).
#   -   A KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.
#       Passing the `expires` option to IARC4 will alter this limit.
#
#   This code should not be considered secure. It has not been cryptanalyzed and should not be used
#   in production. This code is strictly experimental.

from bitstring import BitArray
from Crypto.Random import random

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def random_bytes(n_bytes=512):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   VMPC state preparation without vector
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for n in range(state_size*3):
        i = n % state_size
        j = state[(((j + state[i]) % state_size) + key[i % key_size]) % state_size]

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state


#   Generic error (e.g., key and nonce of different lengths)
class KeyError(Exception): pass

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(KeyError): pass


#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256, expires=8447):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == expires): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering.
def IARC4(key, nonce, drop=8192, expires=255):
    key_length = len(key)

    #   Keys and nonces must be of even, equal lengths, 512 bytes apiece is recommended.
    if ((key_length != len(nonce)) or ((key_length % 2) != 0)):
        raise KeyError

    #   Split key and nonce in half to gain key_a, key_b, nonce_a, and nonce_b
    key_split   = int(key_length / 2)
    key_a       = BitArray(bytes=key[:key_split])
    key_b       = BitArray(bytes=key[key_split:])
    nonce_a     = BitArray(bytes=nonce[:key_split])
    nonce_b     = BitArray(bytes=nonce[key_split:])

    #   XOR keys and nonces.
    key_a = (key_a ^ nonce_a).bytes
    key_b = (key_b ^ nonce_b).bytes

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b, expires=(drop + expires))

    #   Discard first 4096 iterations of each state space
    for dropped in range(drop): next(keystream)

    #   Return the prepared PRNG to begin accepting input
    def _IARC4(text):
        #   Combine the keystream and the text stream
        for key_byte, text_byte in zip(keystream, text):
            yield key_byte ^ text_byte

    return _IARC4

#   Test Vector for IARC4
#
#   nonce:      bytes(255) + b'\x01' + bytes(255) + b'\x02'
#   key:        b'key_a' + bytes(251) + b'key_b' + bytes(251)
#   plaintext:  b'Plaintext'
#   ciphertext: b'B\xfca \xe0\xc5Ic\xa5'

if (__name__ == "__main__"):
    nonce           = bytes(255) + b'\x01' + bytes(255) + b'\x02'
    key             = b'key_a' + bytes(251) + b'key_b' + bytes(251)
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encrypter       = IARC4(key, nonce)
    encryption      = encrypter(plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decrypter       = IARC4(key, nonce)
    decryption      = decrypter(ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" %
    (nonce, key, plaintext, ciphertext))

Revision: 58537
at July 21, 2012 08:33 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of proposed improvements over naive ARC4:
#
#   -   Uses KSA from VMPC minus an IV. (Each key should be combined with a nonce first.)
#   -   Uses 2 state spaces (RC4A). Requires a nonce and key for each state space.
#   -   Takes a nonce alongside each key. They are XOR'd to produce a new key.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192).
#   -   A KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.
#
#   This code should not be considered secure. It has not been cryptanalyzed and should not be used
#   in production. This code is strictly experimental.

from bitstring import BitArray
from Crypto.Random import random

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def random_bytes(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   VMPC state preparation without vector
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for n in range(state_size*3):
        i = n % state_size
        j = state[(((j + state[i]) % state_size) + key[i % key_size]) % state_size]

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(Exception): pass

#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == 8447): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering
def IARC4(key_a, nonce_a, key_b, nonce_b):
    #   XOR keys and nonces. They must be of equal length.
    key_a = (BitArray(bytes=key_a) ^ BitArray(bytes=nonce_a)).bytes
    key_b = (BitArray(bytes=key_b) ^ BitArray(bytes=nonce_b)).bytes

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b)

    #   Discard first 4096 iterations
    for dropped in range(8192): next(keystream)

    #   Return the prepared PRNG to begin accepting input
    def _IARC4(text):
        #   Combine the keystream and the text stream
        for key_byte, text_byte in zip(keystream, text):
            yield key_byte ^ text_byte

    return _IARC4

#   Test Vector for IARC4
#
#   nonce_a:    bytes(255) + b'\x01'
#   key_a:      b'key_a' + bytes(251)
#   nonce_b:    bytes(255) + b'\x02'
#   key_b:      b'key_b' + bytes(251)
#   plaintext:  b'Plaintext'
#   ciphertext: b'B\xfca \xe0\xc5Ic\xa5'

if (__name__ == "__main__"):
    nonce_a         = bytes(255) + b'\x01'
    key_a           = b'key_a' + bytes(251)
    nonce_b         = bytes(255) + b'\x02'
    key_b           = b'key_b' + bytes(251)
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encrypter       = IARC4(key_a, nonce_a, key_b, nonce_b)
    encryption      = encrypter(plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decrypter       = IARC4(key_a, nonce_a, key_b, nonce_b)
    decryption      = decrypter(ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce_a:    %s\nkey_a:      %s\nnonce_b:    %s\nkey_b:      %s\nplaintext:  %s\nciphertext: %s" %
    (nonce_a, key_a, nonce_b, key_b, plaintext, ciphertext))

Revision: 58536
at July 21, 2012 08:26 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of improvements over naive ARC4:
#
#   -   Uses KSA from VMPC minus an IV. (Each key should be combined with a nonce first.)
#   -   Uses 2 state spaces (RC4A). Requires a nonce and key for each state space.
#   -   Takes a nonce alongside each key. They are XOR'd to produce a new key.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192).
#   -   A KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.

from bitstring import BitArray
from Crypto.Random import random

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def random_bytes(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   VMPC state preparation without vector
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for n in range(state_size*3):
        i = n % state_size
        j = state[(((j + state[i]) % state_size) + key[i % key_size]) % state_size]

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(Exception): pass

#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == 8447): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering
def IARC4(key_a, nonce_a, key_b, nonce_b):
    #   XOR keys and nonces. They must be of equal length.
    key_a = (BitArray(bytes=key_a) ^ BitArray(bytes=nonce_a)).bytes
    key_b = (BitArray(bytes=key_b) ^ BitArray(bytes=nonce_b)).bytes

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b)

    #   Discard first 4096 iterations
    for dropped in range(8192): next(keystream)

    #   Return the prepared PRNG to begin accepting input
    def _IARC4(text):
        #   Combine the keystream and the text stream
        for key_byte, text_byte in zip(keystream, text):
            yield key_byte ^ text_byte

    return _IARC4

#   Test Vector for IARC4
#
#   nonce_a:    bytes(255) + b'\x01'
#   key_a:      b'key_a' + bytes(251)
#   nonce_b:    bytes(255) + b'\x02'
#   key_b:      b'key_b' + bytes(251)
#   plaintext:  b'Plaintext'
#   ciphertext: b'B\xfca \xe0\xc5Ic\xa5'

if (__name__ == "__main__"):
    nonce_a         = bytes(255) + b'\x01'
    key_a           = b'key_a' + bytes(251)
    nonce_b         = bytes(255) + b'\x02'
    key_b           = b'key_b' + bytes(251)
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encrypter       = IARC4(key_a, nonce_a, key_b, nonce_b)
    encryption      = encrypter(plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decrypter       = IARC4(key_a, nonce_a, key_b, nonce_b)
    decryption      = decrypter(ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce_a:    %s\nkey_a:      %s\nnonce_b:    %s\nkey_b:      %s\nplaintext:  %s\nciphertext: %s" %
    (nonce_a, key_a, nonce_b, key_b, plaintext, ciphertext))

Revision: 58535
at July 21, 2012 08:21 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of improvements over naive ARC4:
#
#   -   Uses KSA from VMPC minus an IV. (Each key should be combined with a nonce first.)
#   -   Uses 2 state spaces (RC4A). Requires a nonce and key for each state space.
#   -   Takes a nonce alongside each key. They are XOR'd to produce a new key.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192). A
#       KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.

from bitstring import BitArray
from Crypto.Random import random

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def random_bytes(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   VMPC state preparation without vector
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for n in range(state_size*3):
        i = n % state_size
        j = state[(((j + state[i]) % state_size) + key[i % key_size]) % state_size]

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(Exception): pass

#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == 8447): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering
def IARC4(key_a, nonce_a, key_b, nonce_b):
    #   XOR keys and nonces. They must be of equal length.
    key_a = (BitArray(bytes=key_a) ^ BitArray(bytes=nonce_a)).bytes
    key_b = (BitArray(bytes=key_b) ^ BitArray(bytes=nonce_b)).bytes

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b)

    #   Discard first 4096 iterations
    for dropped in range(8192): next(keystream)

    #   Return the prepared PRNG to begin accepting input
    def _IARC4(text):
        #   Combine the keystream and the text stream
        for key_byte, text_byte in zip(keystream, text):
            yield key_byte ^ text_byte

    return _IARC4

#   Test Vector for IARC4
#
#   nonce_a:    bytes(255) + b'\x01'
#   key_a:      b'key_a' + bytes(251)
#   nonce_b:    bytes(255) + b'\x02'
#   key_b:      b'key_b' + bytes(251)
#   plaintext:  b'Plaintext'
#   ciphertext: b'B\xfca \xe0\xc5Ic\xa5'

if (__name__ == "__main__"):
    nonce_a         = bytes(255) + b'\x01'
    key_a           = b'key_a' + bytes(251)
    nonce_b         = bytes(255) + b'\x02'
    key_b           = b'key_b' + bytes(251)
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encrypter       = IARC4(key_a, nonce_a, key_b, nonce_b)
    encryption      = encrypter(plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decrypter       = IARC4(key_a, nonce_a, key_b, nonce_b)
    decryption      = decrypter(ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce_a:    %s\nkey_a:      %s\nnonce_b:    %s\nkey_b:      %s\nplaintext:  %s\nciphertext: %s" %
    (nonce_a, key_a, nonce_b, key_b, plaintext, ciphertext))

Revision: 58534
at July 21, 2012 07:38 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of improvements over naive ARC4:
#
#   -   Uses 2 state spaces (RC4A). Requires a nonce and key for each state space.
#   -   Takes a nonce alongside the key. They are XOR'd to produce a new key.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192). A
#       KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.

from bitstring import BitArray
from Crypto.Random import random

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def random_bytes(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   Standard ARC4 state preparation
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for i in range(state_size):
        j = (j + state[i] + key[i % key_size]) % state_size

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(Exception): pass

#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == 8447): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering
def IARC4(key_a, nonce_a, key_b, nonce_b):
    #   XOR keys and nonces. They must be of equal length.
    key_a = (BitArray(bytes=key_a) ^ BitArray(bytes=nonce_a)).bytes
    key_b = (BitArray(bytes=key_b) ^ BitArray(bytes=nonce_b)).bytes

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b)

    #   Discard first 4096 iterations
    for dropped in range(8192): next(keystream)

    #   Return the prepared PRNG to begin accepting input
    def _IARC4(text):
        #   Combine the keystream and the text stream
        for key_byte, text_byte in zip(keystream, text):
            yield key_byte ^ text_byte

    return _IARC4

#   Test Vector for IARC4
#
#   nonce_a:    bytes(255) + b'\x01'
#   key_a:      b'key_a' + bytes(251)
#   nonce_b:    bytes(255) + b'\x02'
#   key_b:      b'key_b' + bytes(251)
#   plaintext:  b'Plaintext'
#   ciphertext: b'n\xc7\x88\xefF>G\xe5\x03'

if (__name__ == "__main__"):
    nonce_a         = bytes(255) + b'\x01'
    key_a           = b'key_a' + bytes(251)
    nonce_b         = bytes(255) + b'\x02'
    key_b           = b'key_b' + bytes(251)
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encrypter       = IARC4(key_a, nonce_a, key_b, nonce_b)
    encryption      = encrypter(plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decrypter       = IARC4(key_a, nonce_a, key_b, nonce_b)
    decryption      = decrypter(ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce_a:    %s\nkey_a:      %s\nnonce_b:    %s\nkey_b:      %s\nplaintext:  %s\nciphertext: %s" %
    (nonce_a, key_a, nonce_b, key_b, plaintext, ciphertext))

Revision: 58533
at July 21, 2012 07:23 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of improvements over naive ARC4:
#
#   -   Uses 2 state spaces (RC4A). Requires a nonce and key for each state space.
#   -   Takes a nonce alongside the key. They are XOR'd to produce a new key.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192). A
#       KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.

from bitstring import BitArray
from Crypto.Hash import RIPEMD
from Crypto.Random import random

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def random_bytes(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   Standard ARC4 state preparation
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for i in range(state_size):
        j = (j + state[i] + key[i % key_size]) % state_size

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(Exception): pass

#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == 8447): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering
def IARC4(key_a, nonce_a, key_b, nonce_b):
    #   XOR keys and nonces. They must be of equal length.
    key_a = (BitArray(bytes=key_a) ^ BitArray(bytes=nonce_a)).bytes
    key_b = (BitArray(bytes=key_b) ^ BitArray(bytes=nonce_b)).bytes

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b)

    #   Discard first 4096 iterations
    for dropped in range(8192): next(keystream)

    #   Return the prepared PRNG to begin accepting input
    def _IARC4(text):
        #   Combine the keystream and the text stream
        for key_byte, text_byte in zip(keystream, text):
            yield key_byte ^ text_byte

    return _IARC4

#   Test Vector for IARC4
#
#   nonce_a:    bytes(255) + b'\x01'
#   key_a:      b'key_a' + bytes(251)
#   nonce_b:    bytes(255) + b'\x02'
#   key_b:      b'key_b' + bytes(251)
#   plaintext:  b'Plaintext'
#   ciphertext: b'n\xc7\x88\xefF>G\xe5\x03'

if (__name__ == "__main__"):
    from pprint import pprint as pp

    nonce_a         = bytes(255) + b'\x01'
    key_a           = b'key_a' + bytes(251)
    nonce_b         = bytes(255) + b'\x02'
    key_b           = b'key_b' + bytes(251)
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encrypter       = IARC4(key_a, nonce_a, key_b, nonce_b)
    encryption      = encrypter(plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decrypter       = IARC4(key_a, nonce_a, key_b, nonce_b)
    decryption      = decrypter(ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce_a:    %s\nkey_a:      %s\nnonce_b:    %s\nkey_b:      %s\nplaintext:  %s\nciphertext: %s" %
    (nonce_a, key_a, nonce_b, key_b, plaintext, ciphertext))

Revision: 58532
at July 20, 2012 08:19 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of improvements over naive ARC4:
#
#   -   Takes a nonce alongside the key. They are padded (HMAC-style), XOR'd, and hashed with
#       RIPEMD to produce a new key.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192). A
#       KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.
#   -   Uses 2 state spaces (RC4A). The key resulting from the key and nonce is expanded to
#       generate a key for each state space.

from bitstring import BitArray
from Crypto.Hash import RIPEMD
from Crypto.Random import random

#   HMAC-style padding for key and nonce
def pad_key(hasher, key):
    block_size = hasher.digest_size

    if (len(key) > block_size):
        hasher.update(key)
        return hasher.digest()
    elif (len(key) < block_size):
        return key + (b'\x00' * (block_size - len(key)))

    return key

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def generate_nonce(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   Key expansion function which tries not to reduce the size of the input to the hash function.
#
#   key_a(key)  = hash(hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])))
#   key_b(key)  = hash(hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])) ^
#                   (hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])))[(key.length/2):] +
#                   hash(key ^ (key[(key.length/2):] + key[:(key.length/2)]))[:(key.length/2)])
def expand_key(hasher, key):
    hasher_a    = hasher.copy()
    hasher_b    = hasher.copy()
    key         = BitArray(bytes=key)
    split       = int(key.length / 2)

    #   XOR the key against itself with its halves swapped, then hash
    key         = key ^ (key[split:] + key[:split])
    hasher.update(key.bytes)
    key         = BitArray(bytes=hasher.digest())

    #   Key A is the hash of the previous hash
    #   Key B is the hash of the previous hash XOR'd against itself with its halves swapped.
    hasher_a.update(key.bytes)
    hasher_b.update((key ^ (key[split:] + key[:split])).bytes)

    return (hasher_a.digest(), hasher_b.digest())

#   Standard ARC4 state preparation
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for i in range(state_size):
        j = (j + state[i] + key[i % key_size]) % state_size

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(Exception): pass

#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == 8447): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering
def IARC4(key, nonce):
    #   Pad both key and nonce using HMAC-style padding function
    hasher      = RIPEMD.new()
    key         = pad_key(hasher.copy(), key)
    nonce       = pad_key(hasher.copy(), nonce)

    #   Combine key and nonce by hash(key ^ nonce)
    hasher.update((BitArray(bytes=key) ^ BitArray(bytes=nonce)).bytes)
    key         = hasher.digest()

    #   Expand the key into two keys for the two state spaces
    key_a, key_b = expand_key(hasher.copy(), key)

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b)

    #   Discard first 4096 iterations
    for dropped in range(8192): next(keystream)

    #   Return the prepared PRNG to begin accepting input
    def _IARC4(text):
        #   Combine the keystream and the text stream
        for key_byte, text_byte in zip(keystream, text):
            yield key_byte ^ text_byte

    return _IARC4

#   Test Vectors for IARC4
#
#   nonce:      b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
#   key:        b'Key'
#   plaintext:  b'Plaintext'
#   ciphertext: b',\xe6K\xfbUW\xf2f\xee'
#
#   nonce:      b'Nonce'
#   key:        b'Key'
#   plaintext:  b'Plaintext'
#   ciphertext: b'\xef\xedI\x9cd\xd6w\xd9\x15'

if (__name__ == "__main__"):
    from pprint import pprint as pp

    key             = b'Key'
    nonce           = generate_nonce()
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encrypter       = IARC4(key, nonce)
    encryption      = encrypter(plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decrypter       = IARC4(key, nonce)
    decryption      = decrypter(ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" % (nonce, key, plaintext, ciphertext))

Revision: 58531
at July 20, 2012 07:14 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of improvements over naive ARC4:
#
#   -   Takes a nonce alongside the key. They are padded (HMAC-style), XOR'd, and hashed with
#       RIPEMD to produce a new key.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192). A
#       KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.
#   -   Uses 2 state spaces (RC4A). The key resulting from the key and nonce is expanded to
#       generate a key for each state space.

from bitstring import BitArray
from Crypto.Hash import RIPEMD
from Crypto.Random import random

#   HMAC-style padding for key and nonce
def pad_key(hasher, key):
    block_size = hasher.digest_size

    if (len(key) > block_size):
        hasher.update(key)
        return hasher.digest()
    elif (len(key) < block_size):
        return key + (b'\x00' * (block_size - len(key)))

    return key

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def generate_nonce(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   Key expansion function which tries not to reduce the size of the input to the hash function.
#
#   key_a(key)  = hash(hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])))
#   key_b(key)  = hash(hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])) ^
#                   (hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])))[(key.length/2):] +
#                   hash(key ^ (key[(key.length/2):] + key[:(key.length/2)]))[:(key.length/2)])
def expand_key(hasher, key):
    hasher_a    = hasher.copy()
    hasher_b    = hasher.copy()
    key         = BitArray(bytes=key)
    split       = int(key.length / 2)

    #   XOR the key against itself with its halves swapped, then hash
    key         = key ^ (key[split:] + key[:split])
    hasher.update(key.bytes)
    key         = BitArray(bytes=hasher.digest())

    #   Key A is the hash of the previous hash
    #   Key B is the hash of the previous hash XOR'd against itself with its halves swapped.
    hasher_a.update(key.bytes)
    hasher_b.update((key ^ (key[split:] + key[:split])).bytes)

    return (hasher_a.digest(), hasher_b.digest())

#   Standard ARC4 state preparation
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for i in range(state_size):
        j = (j + state[i] + key[i % key_size]) % state_size

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(Exception): pass

#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == 8447): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering
def IARC4(key, nonce, text):
    #   Pad both key and nonce using HMAC-style padding function
    hasher      = RIPEMD.new()
    key         = pad_key(hasher.copy(), key)
    nonce       = pad_key(hasher.copy(), nonce)

    #   Combine key and nonce by hash(key ^ nonce)
    hasher.update((BitArray(bytes=key) ^ BitArray(bytes=nonce)).bytes)
    key         = hasher.digest()

    #   Expand the key into two keys for the two state spaces
    key_a, key_b = expand_key(hasher.copy(), key)

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b)

    #   Discard first 4096 iterations
    for dropped in range(8192): next(keystream)

    #   Combine the keystream and the text stream
    for key_byte, text_byte in zip(keystream, text):
        yield key_byte ^ text_byte

#   Test Vectors for IARC4
#
#   nonce:      b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
#   key:        b'Key'
#   plaintext:  b'Plaintext'
#   ciphertext: b',\xe6K\xfbUW\xf2f\xee'
#
#   nonce:      b'Nonce'
#   key:        b'Key'
#   plaintext:  b'Plaintext'
#   ciphertext: b'\xef\xedI\x9cd\xd6w\xd9\x15'

if (__name__ == "__main__"):
    from pprint import pprint as pp

    key             = b'Key'
    nonce           = generate_nonce()
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encryption      = IARC4(key, nonce, plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decryption      = IARC4(key, nonce, ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" % (nonce, key, plaintext, ciphertext))

Revision: 58530
at July 20, 2012 07:10 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of improvements over naive ARC4:
#
#   -   Takes a nonce alongside the key. They are padded (HMAC-style), XOR'd, and hashed with
#       RIPEMD to produce a new key.
#   -   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192). A
#       KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop.
#   -   Uses 2 state spaces (RC4A). The key resulting from the key and nonce is expanded to
#       generate a key for each state space.

from bitstring import BitArray
from Crypto.Hash import RIPEMD
from Crypto.Random import random

#   HMAC-style padding for key and nonce
def pad_key(hasher, key):
    block_size = hasher.digest_size

    if (len(key) > block_size):
        hasher.update(key)
        return hasher.digest()
    elif (len(key) < block_size):
        return key + (b'\x00' * (block_size - len(key)))

    return key

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def generate_nonce(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   Key expansion function which tries not to reduce the size of the input to the hash function.
#
#   key_a(key)  = hash(hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])))
#   key_b(key)  = hash(hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])) ^
#                   (hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])))[(key.length/2):] +
#                   hash(key ^ (key[(key.length/2):] + key[:(key.length/2)]))[:(key.length/2)])
def expand_key(hasher, key):
    hasher_a    = hasher.copy()
    hasher_b    = hasher.copy()
    key         = BitArray(bytes=key)
    split       = int(key.length / 2)

    #   XOR the key against itself with its halves swapped, then hash
    key         = key ^ (key[split:] + key[:split])
    hasher.update(key.bytes)
    key         = BitArray(bytes=hasher.digest())

    #   Key A is the hash of the previous hash
    #   Key B is the hash of the previous hash XOR'd against itself with its halves swapped.
    hasher_a.update(key.bytes)
    hasher_b.update((key ^ (key[split:] + key[:split])).bytes)

    return (hasher_a.digest(), hasher_b.digest())

#   Standard ARC4 state preparation
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for i in range(state_size):
        j = (j + state[i] + key[i % key_size]) % state_size

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(Exception): pass

#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    while True:
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        #   Limit the PRNG to 255 iterations after dropping 8192 iterations
        if (n == 8447): break
        n += 1

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering
def IARC4(key, nonce, text):
    #   Pad both key and nonce using HMAC-style padding function
    hasher      = RIPEMD.new()
    key         = pad_key(hasher.copy(), key)
    nonce       = pad_key(hasher.copy(), nonce)

    #   Combine key and nonce by hash(key ^ nonce)
    hasher.update((BitArray(bytes=key) ^ BitArray(bytes=nonce)).bytes)
    key         = hasher.digest()

    #   Expand the key into two keys for the two state spaces
    key_a, key_b = expand_key(hasher.copy(), key)

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b)

    #   Discard first 4096 iterations
    for dropped in range(8192): next(keystream)

    #   Combine the keystream and the text stream
    for key_byte, text_byte in zip(keystream, text):
        yield key_byte ^ text_byte

#   Test Vectors for IARC4
#
#   nonce:      b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
#   key:        b'Key'
#   plaintext:  b'Plaintext'
#   ciphertext: b',\xe6K\xfbUW\xf2f\xee'
#
#   key:        b'Key'
#   nonce:      b'Nonce'
#   plaintext:  b'Plaintext'
#   ciphertext: b'\xef\xedI\x9cd\xd6w\xd9\x15'

if (__name__ == "__main__"):
    from pprint import pprint as pp

    key             = b'Key'
    nonce           = generate_nonce()
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encryption      = IARC4(key, nonce, plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decryption      = IARC4(key, nonce, ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" % (nonce, key, plaintext, ciphertext))

Revision: 58529
at July 20, 2012 06:58 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of improvements over naive ARC4:
#
#   -   Takes a nonce alongside the key. They are padded (HMAC-style), XOR'd, and hashed with
#       RIPEMD to produce a new key.
#   -   Drops the first 4096 iterations of the PRNG's state (RC4-drop4096). A KeyExpiredError will
#       be raised after 255 iterations of the PRNG, excluding the initial drop.
#   -   Uses 2 state spaces (RC4A). The key resulting from the key and nonce is expanded to
#       generate a key for each state space.

from bitstring import BitArray
from Crypto.Hash import RIPEMD
from Crypto.Random import random

#   HMAC-style padding for key and nonce
def pad_key(hasher, key):
    block_size = hasher.digest_size

    if (len(key) > block_size):
        hasher.update(key)
        return hasher.digest()
    elif (len(key) < block_size):
        return key + (b'\x00' * (block_size - len(key)))

    return key

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def generate_nonce(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   Key expansion function which tries not to reduce the size of the input to the hash function.
#
#   key_a(key)  = hash(hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])))
#   key_b(key)  = hash(hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])) ^
#                   (hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])))[(key.length/2):] +
#                   hash(key ^ (key[(key.length/2):] + key[:(key.length/2)]))[:(key.length/2)])
def expand_key(hasher, key):
    hasher_a    = hasher.copy()
    hasher_b    = hasher.copy()
    key         = BitArray(bytes=key)
    split       = int(key.length / 2)

    #   XOR the key against itself with its halves swapped, then hash
    key         = key ^ (key[split:] + key[:split])
    hasher.update(key.bytes)
    key         = BitArray(bytes=hasher.digest())

    #   Key A is the hash of the previous hash
    #   Key B is the hash of the previous hash XOR'd against itself with its halves swapped.
    hasher_a.update(key.bytes)
    hasher_b.update((key ^ (key[split:] + key[:split])).bytes)

    return (hasher_a.digest(), hasher_b.digest())

#   Standard ARC4 state preparation
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for i in range(state_size):
        j = (j + state[i] + key[i % key_size]) % state_size

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(Exception): pass

#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    #   Limit the PRNG to 255 iterations after dropping 4096 iterations
    while (n < 4351):
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering
def IARC4(key, nonce, text):
    #   Pad both key and nonce using HMAC-style padding function
    hasher      = RIPEMD.new()
    key         = pad_key(hasher.copy(), key)
    nonce       = pad_key(hasher.copy(), nonce)

    #   Combine key and nonce by hash(key ^ nonce)
    hasher.update((BitArray(bytes=key) ^ BitArray(bytes=nonce)).bytes)
    key         = hasher.digest()

    #   Expand the key into two keys for the two state spaces
    key_a, key_b = expand_key(hasher.copy(), key)

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b)

    #   Discard first 4096 iterations
    for dropped in range(4096): next(keystream)

    #   Combine the keystream and the text stream
    for key_byte, text_byte in zip(keystream, text):
        yield key_byte ^ text_byte

#   Test Vectors for IARC4
#
#   nonce:      b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
#   key:        b'Key'
#   plaintext:  b'Plaintext'
#   ciphertext: b'\x9f\x0b;\xf7w\xcc\xb3\xcd\xec'
#
#   key:        b'Key'
#   nonce:      b'Nonce'
#   plaintext:  b'Plaintext'
#   ciphertext: b'\xd5\xfc\xa7\xc7c\x9f\xab1\x97'

if (__name__ == "__main__"):
    from pprint import pprint as pp

    key             = b'Key'
    nonce           = b'Nonce'
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encryption      = IARC4(key, nonce, plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decryption      = IARC4(key, nonce, ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" % (nonce, key, plaintext, ciphertext))

Revision: 58528
at July 20, 2012 06:54 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of improvements over naive ARC4:
#
#   -   Takes a nonce alongside the key. They are padded (HMAC-style), XOR'd, and hashed with
#       RIPEMD to produce a new key.
#   -   Drops the first 4096 iterations of the PRNG's state (RC4-drop4096). A KeyExpiredError will
#       be raised after 255 iterations of the PRNG, excluding the initial drop.
#   -   Uses 2 state spaces (RC4A). The key resulting from the key and nonce is expanded to
#       generate a key for each state space.

from bitstring import BitArray
from Crypto.Hash import RIPEMD
from Crypto.Random import random

#   HMAC-style padding for key and nonce
def pad_key(hasher, key):
    block_size = hasher.digest_size

    if (len(key) > block_size):
        hasher.update(key)
        return hasher.digest()
    elif (len(key) < block_size):
        return key + (b'\x00' * (block_size - len(key)))

    return key

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def generate_nonce(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   Key expansion function which tries not to reduce the size of the input to the hash function.
#
#   key_a(key)  = hash(hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])))
#   key_b(key)  = hash(hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])) ^
#                   (hash(key ^ (key[(key.length/2):] + key[:(key.length/2)])))[(key.length/2):] +
#                   hash(key ^ (key[(key.length/2):] + key[:(key.length/2)]))[:(key.length/2)])
def expand_key(hasher, key):
    hasher_a    = hasher.copy()
    hasher_b    = hasher.copy()
    key         = BitArray(bytes=key)
    split       = int(key.length / 2)

    #   XOR the key against itself with its halves swapped, then hash
    key         = key ^ (key[split:] + key[:split])
    hasher.update(key.bytes)
    key         = BitArray(bytes=hasher.digest())

    #   Key A is the hash of the previous hash
    #   Key B is the hash of the previous hash XOR'd against itself with its halves swapped.
    hasher_a.update(key.bytes)
    hasher_b.update((key ^ (key[split:] + key[:split])).bytes)

    return (hasher.digest(), hasher_b.digest())

#   Standard ARC4 state preparation
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for i in range(state_size):
        j = (j + state[i] + key[i % key_size]) % state_size

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(Exception): pass

#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    #   Limit the PRNG to 255 iterations after dropping 4096 iterations
    while (n < 4351):
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering
def IARC4(key, nonce, text):
    #   Pad both key and nonce using HMAC-style padding function
    hasher      = RIPEMD.new()
    key         = pad_key(hasher.copy(), key)
    nonce       = pad_key(hasher.copy(), nonce)

    #   Combine key and nonce by hash(key ^ nonce)
    hasher.update((BitArray(bytes=key) ^ BitArray(bytes=nonce)).bytes)
    key         = hasher.digest()

    #   Expand the key into two keys for the two state spaces
    key_a, key_b = expand_key(hasher.copy(), key)

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b)

    #   Discard first 4096 iterations
    for dropped in range(4096): next(keystream)

    #   Combine the keystream and the text stream
    for key_byte, text_byte in zip(keystream, text):
        yield key_byte ^ text_byte

#   Test Vectors for IARC4
#
#   nonce:      b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
#   key:        b'Key'
#   plaintext:  b'Plaintext'
#   ciphertext: b'\x01\xccd\xecaS\xc2&\x16'
#
#   key:        b'Key'
#   nonce:      b'Nonce'
#   plaintext:  b'Plaintext'
#   ciphertext: b'\x04;\xf7\x98\xb1Y\x81\x87C'

if (__name__ == "__main__"):
    from pprint import pprint as pp

    key             = b'Key'
    nonce           = generate_nonce()
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encryption      = IARC4(key, nonce, plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decryption      = IARC4(key, nonce, ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" % (nonce, key, plaintext, ciphertext))

Revision: 58527
at July 20, 2012 06:34 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of improvements over naive ARC4:
#
#   -   Takes a nonce alongside the key. They are padded (HMAC-style), XOR'd, and hashed with
#       RIPEMD to produce a new key.
#   -   Drops the first 4096 iterations of the PRNG's state (RC4-drop4096). A KeyExpiredError will
#       be raised after 255 iterations of the PRNG, excluding the initial drop.
#   -   Uses 2 state spaces (RC4A). The key resulting from the key and nonce is futher split,
#       XOR'd, split, and the halves hashed with RIPEMD to generate a key for each state space.

from bitstring import BitArray
from Crypto.Hash import RIPEMD
from Crypto.Random import random

#   HMAC-style padding for key and nonce
def pad_key(hasher, key):
    block_size = hasher.digest_size

    if (len(key) > block_size):
        hasher.update(key)
        return hasher.digest()
    elif (len(key) < block_size):
        return key + (b'\x00' * (block_size - len(key)))

    return key

#   Generates a random n-byte string. Makes no checks to ensure uniqueness.
def generate_nonce(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

#   Split the key in half, XOR the halves together, hash each half of the result to yield 2 new keys.
#   This assumes your input key has already been padded, XOR'd with a padded nonce, and hashed.
def expand_key(hasher, key):
    key         = BitArray(bytes=key)
    split       = int(key.length / 2)
    key         = (key[:split] ^ key[split:]).bytes
    hasher_b    = hasher.copy()
    hasher.update(key[:split])
    hasher_b.update(key[split:])

    return (hasher.digest(), hasher_b.digest())

#   Standard ARC4 state preparation
def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for i in range(state_size):
        j = (j + state[i] + key[i % key_size]) % state_size

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

#   Raised if a key/nonce combination has been used too many times already (255).
class KeyExpiredError(Exception): pass

#   Uses 2 state spaces (RC4A)
def pseudorandom_generator(state_a, state_b, state_size=256):
    i       = 0
    j_a     = 0
    j_b     = 0
    n       = 0

    #   Limit the PRNG to 255 iterations after dropping 4096 iterations
    while (n < 4351):
        n   += 1

        i   = (i + 1) % state_size
        j_a = (j_a + state_a[i]) % state_size

        state_a[i]   ^= state_a[j_a]
        state_a[j_a] ^= state_a[i]
        state_a[i]   ^= state_a[j_a]

        yield state_b[(state_a[i] + state_a[j_a]) % state_size]

        j_b = (j_b + state_b[i]) % state_size

        state_b[i]   ^= state_b[j_b]
        state_b[j_b] ^= state_b[i]
        state_b[i]   ^= state_b[j_b]

        yield state_a[(state_b[i] + state_b[j_b]) % state_size]

    raise KeyExpiredError

#   Main keystream generation and ciphering
def IARC4(key, nonce, text):
    #   Pad both key and nonce using HMAC-style padding function
    hasher      = RIPEMD.new()
    key         = pad_key(hasher.copy(), key)
    nonce       = pad_key(hasher.copy(), nonce)

    #   Combine key and nonce by hash(key ^ nonce)
    hasher.update((BitArray(bytes=key) ^ BitArray(bytes=nonce)).bytes)
    key         = hasher.digest()

    #   Expand the key into two keys for the two state spaces
    key_a, key_b = expand_key(hasher.copy(), key)

    #   Prepare the PRNG
    state_a     = prepare_state(key_a)
    state_b     = prepare_state(key_b)
    keystream   = pseudorandom_generator(state_a, state_b)

    #   Discard first 4096 iterations
    for dropped in range(4096): next(keystream)

    #   Combine the keystream and the text stream
    for key_byte, text_byte in zip(keystream, text):
        yield key_byte ^ text_byte

#   Test Vectors for IARC4
#
#   nonce:      b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
#   key:        b'Key'
#   plaintext:  b'Plaintext'
#   ciphertext: b'"\xe3\xb4\x1e\x17\xe8W\xa5\xb8'
#
#   key:        b'Key'
#   nonce:      b'Nonce'
#   plaintext:  b'Plaintext'
#   ciphertext: b'K\xf8\x8f\x1c\x8bA\xbd\xff\xcc'

if (__name__ == "__main__"):
    from pprint import pprint as pp

    key             = b'Key'
    nonce           = generate_nonce()
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encryption      = IARC4(key, nonce, plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decryption      = IARC4(key, nonce, ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" % (nonce, key, plaintext, ciphertext))

Revision: 58526
at July 20, 2012 05:52 by weilawei


Updated Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of improvements over naive ARC4:
#
#   -   Takes a nonce alongside the key. They are padded, XOR'd, and hashed to produce a new key.
#   -   Drops the first 4096 iterations of the PRNG's state. (RC4-drop4096)
#   -   A KeyExpiredError will be raised after 255 iterations of the PRNG, excluding the initial drop.

from bitstring import BitArray
from Crypto.Hash import RIPEMD
from Crypto.Random import random

#   HMAC-style padding for key and nonce
def pad_key(hasher, key):
    block_size = hasher.digest_size

    if (len(key) > block_size):
        hasher.update(key)
        return hasher.digest()
    elif (len(key) < block_size):
        return key + (b'\x00' * (block_size - len(key)))

    return key

def generate_nonce(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for i in range(state_size):
        j = (j + state[i] + key[i % key_size]) % state_size

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

class KeyExpiredError(Exception): pass

def pseudorandom_generator(state):
    size    = len(state)
    i       = 0
    j       = 0
    n       = 0

    #   Limit the PRNG to 255 iterations after dropping 4096 iterations
    while (n < 4351):
        n += 1
        i = (i + 1) % size
        j = (j + state[i]) % size

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

        yield state[(state[i] + state[j]) % size]

    raise KeyExpiredError

def IARC4(key, nonce, text):
    #   Pad both key and nonce using HMAC-style padding function
    hasher      = RIPEMD.new()
    key         = pad_key(hasher.copy(), key)
    nonce       = pad_key(hasher.copy(), nonce)

    #   Combine key and nonce by hash(key ^ nonce)
    hasher.update((BitArray(bytes=key) ^ BitArray(bytes=nonce)).bytes)
    key         = hasher.digest()

    #   Prepare the PRNG
    state       = prepare_state(key)
    keystream   = pseudorandom_generator(state)

    #   Discard first 4096 iterations
    for dropped in range(4096): next(keystream)

    #   Combine the keystream and the text stream
    for key_byte, text_byte in zip(keystream, text):
        yield key_byte ^ text_byte

#   Test Vectors for IARC4
#
#   nonce:      b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
#   key:        b'Key'
#   plaintext:  b'Plaintext'
#   ciphertext: b'\xa8\x01\x02\xcdSt\xfc\x9a\xfc'
#
#   key:        b'Key'
#   nonce:      b'Nonce'
#   plaintext:  b'Plaintext'
#   ciphertext: b'Znb\xa7\xe1\xe2\x9bh\x0c'

if (__name__ == "__main__"):
    from pprint import pprint as pp

    key             = b'Key'
    nonce           = generate_nonce()
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encryption      = IARC4(key, nonce, plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decryption      = IARC4(key, nonce, ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" % (nonce, key, plaintext, ciphertext))

Revision: 58525
at July 20, 2012 05:48 by weilawei


Initial Code
#!/usr/bin/env python3

#   This code is public domain.
#
#   Improved ARC4 (IARC4) contains a number of improvements over naive ARC4:
#
#   -   Takes a nonce alongside the key. They are padded, XOR'd, and hashed to produce a new key.
#   -   Drops the first 4096 iterations of the PRNG's state. (RC4-drop4096)
#   -   A KeyExpiredError will be raised after 255 iterations of the PRNG, excluding the initial drop.

from bitstring import BitArray
from Crypto.Hash import RIPEMD
from Crypto.Random import random

#   HMAC-style padding for key and nonce
def pad_key(hasher, key):
    block_size = hasher.digest_size

    if (len(key) > block_size):
        hasher.update(key)
        return hasher.digest()
    elif (len(key) < block_size):
        return key + (b'\x00' * (block_size - len(key)))

    return key

def generate_nonce(n_bytes=256):
    return BitArray(uint=random.getrandbits(n_bytes*8), length=(n_bytes*8)).bytes

def prepare_state(key, state_size=256):
    key_size    = len(key)
    state       = [i for i in range(state_size)]
    j           = 0

    for i in range(state_size):
        j = (j + state[i] + key[i % key_size]) % state_size

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

    return state

class KeyExpiredError(Exception): pass

def pseudorandom_generator(state):
    size    = len(state)
    i       = 0
    j       = 0
    n       = 0

    #   Limit the PRNG to 255 iterations after dropping 4096 iterations
    while True and (n < 4351):
        n += 1
        i = (i + 1) % size
        j = (j + state[i]) % size

        state[i] ^= state[j]
        state[j] ^= state[i]
        state[i] ^= state[j]

        yield state[(state[i] + state[j]) % size]

    raise KeyExpiredError

def IARC4(key, nonce, text):
    #   Pad both key and nonce using HMAC-style padding function
    hasher      = RIPEMD.new()
    key         = pad_key(hasher.copy(), key)
    nonce       = pad_key(hasher.copy(), nonce)

    #   Combine key and nonce by hash(key ^ nonce)
    hasher.update((BitArray(bytes=key) ^ BitArray(bytes=nonce)).bytes)
    key         = hasher.digest()

    #   Prepare the PRNG
    state       = prepare_state(key)
    keystream   = pseudorandom_generator(state)

    #   Discard first 4096 iterations
    for dropped in range(4096): next(keystream)

    #   Combine the keystream and the text stream
    for key_byte, text_byte in zip(keystream, text):
        yield key_byte ^ text_byte

#   Test Vectors for IARC4
#
#   nonce:      b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
#   key:        b'Key'
#   plaintext:  b'Plaintext'
#   ciphertext: b'\xa8\x01\x02\xcdSt\xfc\x9a\xfc'
#
#   key:        b'Key'
#   nonce:      b'Nonce'
#   plaintext:  b'Plaintext'
#   ciphertext: b'Znb\xa7\xe1\xe2\x9bh\x0c'

if (__name__ == "__main__"):
    from pprint import pprint as pp

    key             = b'Key'
    nonce           = generate_nonce()
    plaintext       = b'Plaintext'
    stream_length   = len(plaintext)
    encryption      = IARC4(key, nonce, plaintext)
    ciphertext      = bytes([next(encryption) for i in range(stream_length)])
    decryption      = IARC4(key, nonce, ciphertext)
    plaintext       = bytes([next(decryption) for i in range(stream_length)])

    print("nonce:      %s\nkey:        %s\nplaintext:  %s\nciphertext: %s" % (nonce, key, plaintext, ciphertext))

Initial URL


Initial Description
This code is public domain.

Improved ARC4 (IARC4) contains a number of proposed improvements over naive ARC4:

-   Uses KSA from VMPC minus an IV.
-   Uses 2 state spaces (RC4A). Splits the key and nonce to produce a key and nonce for each state space. Each subkey and subnonce is XOR'd together to produce a new subkey. **TODO**: They should be hashed, but they are not currently, until I select a hash function with an appropriately sized output, which won't limit the keyspace available to IARC4.
-   Takes a nonce alongside the key. The key and nonce must be random and of even, equal length, with 512 bytes per key/nonce suggested.
-   Drops the first 8192 (4096 per state space) iterations of the PRNG (RC4-drop8192).
-   A KeyExpiredError is raised after 255 iterations of the PRNG, excluding the initial drop. Passing the `expires` option to IARC4 will alter this limit.

This code should not be considered secure. It has not been cryptanalyzed and should not be used in production. This code is strictly experimental.

Initial Title
Improved ARC4 (IARC4)

Initial Tags


Initial Language
Python