Blind-ID library for user identification using RSA blind signatures Copyright (C) 2010 Rudolf Polzer Cryptographic protocol description Each user identifies by an ID, signed by a certificate authority owning a private key. Common parameters: - a security parameter k (sized to be viable for a RSA modulus) - a small parameter k0 (for Schnorr identification) - a prime p of size k-1, where (p-1)/2 is a prime too - a generator g of a cyclic multiplicative subgroup of G of Z_p (i.e. a square in Z_p); in our implementation, this is always 4; this group has order (p-1)/2 - a hash function h: bitstring -> bitstring of short output; in our implementation, this is SHA-1 - for each n, a hash function h': Z_n -> Z_n; in our implementation, this is given below - for each n > p, a canonical injection I from Z_p to Z_n A public key consists of: - a RSA modulus n of size k, where n > p - a RSA public key e; in our implementation, we always choose 65537 - the "fingerprint" is base64(SHA1("n || e")) A private key additionally consists of: - a RSA private key d A public ID consists of: - a value S in G - a value H = h'(I(S)) in Z_n - the "fingerprint" is base64(SHA1("S")) A private ID additionally consists of:G - a value s in [0, |G|[, with S = g^s in G ID generation protocol: - Client generates s in [0, |G|[ at random - Client calculates S = g^s in G - Client generates a camouflage value c in Z*_n at random - Client sends x = c^e * h'(I(S)) in Z_n - Server receives x - Server sends y = x^d in Z_n - Client receives y - Client calculates H = y * c^-1 in Z_n Note that this is a RSA blind signature - the value the server receives is distributed uniformly in Z*_n, assuming h'(I(S)) is member of Z*_n which we can assume it is (otherwise we can find a factorization of the RSA modulus n). H obviously fulfills H = h'(I(S)) in Z_n. The goal of this is to make it impossible for the server to know the ID that has been signed, to make the ID not traceable even if the server identifies the user performing the signing. Authentication protocol: Client provides a message m that is to be signed as part of the protocol "start": - Client sends S, H if this is the first round of the protocol - Client generates r in [0, |G|[ at random - Client generates t in [0, |G|[ at random - Client sends x = h("g^r || g^t || m || g^r || g^t") - Client sends m in plain "challenge": - Server receives S, H if this is the first round of the protocol - Server verifies H = h'(I(S)) - Server receives x, m - Server generates c in [0, 2^k0[ at random - Server generates T in [0, |G|[ at random - Server sends c and g^T "response": - Client receives c and g^T - Client verifies that the received values are in the allowed ranges - Client sends y = r - s * c mod |G| - Client sends g^t - Client calculates K = (g^T)^t "verify": - Server receives y and g^t - Server calculates z = g^y S^c - Server calculates x' = h("z || g^t || m || z || g^t") - Server verifies x == x' - Server calculates K = (g^t)^T Protocol variant: g and G can be also part of the public ID. In this case, g and G are sent as part of this protocol additionally to S, H. The protocols executed here are RSA signature, Schnorr identification and a Diffie Hellmann key exchange at the same time. The DH key exchange generates the same values on both sides only if the Schnorr identification scheme succeeds. If the protocol succeeds, the authenticity of m has been verified too. Signature protocol: Client provides a message m that is to be signed as part of the protocol "start": - Client sends S, H if this is the first round of the protocol - Client generates r in [0, |G|[ at random - Client sends c = h("m || g^r") - Client sends y = r - s * c - Client sends m in plain "verify": - Server receives c, y, and m - Server calculates z = g^y S^c - Server calculates c' = h("m || z") - Server verifies c == c' Low level protocol: - "VLI" are sent in the format: - a sequence of bytes in little endian order (one continuation bit + 7 bits per byte) - terminated by cont == 0 - "packets" are sent in the format: - VLI length - data - "numbers" are sent in the format: - packet of the number's digits in big endian order, preceded by a sign byte (0x03 = negative, 0x01 = positive, 0x00 = zero) - all values are sent as "numbers", except for x which is sent raw - strings (m) are sent as "packets" - the || operation inside double quotes is defined in terms of this protocol, so h(z || m || z) actually encodes z as a "number" and m as a "packet" - a value in double quotes is also defined in terms of this protocol, i.e. the length is preceded NOTE: to generate NON blind IDs, the process is not very straightforward. It works like this: Server shall: - load private key Both shall: - perform authentication as usual Server shall: - notice that the status is false - call d0_blind_id_authenticate_with_private_id_generate_missing_signature - write public ID - send that data to client Client shall: - read own private ID - get fingerprint - read received public ID (leaves the private part alone) - verify fingerprint - possibly verify ID - write own private ID again This ensures that only the ID the client authenticated with is signed by the server