To encrypt the transfer of data (and authorization information) we might use a stream cipher or block cipher. Either rely on both ends using a shared key (a number) that that is the seed or initialization value of the cipher. For purposes of this course we will discuss stream ciphers as the mechanism of block ciphers is a bit more involved, and this is not a proper cryptography course.
The key that is shared between the two who wish to communicate in secret is used as the seed of a pseudo-random number generator (PRNG). For any given seed a PRNG will always generate the same sequence of "random" looking values, and so long as both the client and server use the same seed, with the same PRNG, they will each get the same sequence of values from it. The data to be sent/received is then transformed using the XOR function with the PRNG.
Given: k = The shared key. m = a message to be sent / received. e = The encrypted version of m. PRNGk = The PRNG seeded with k. ^ = the XOR (Exclusive OR) function
^
Then: e = m ^ PRNGk m = e ^ PRNGk
This works because the XOR function is a "reversible" function.
Normally the shared key (k) would be determined using a key-exchange protocol such as Diffie-Helman or the RSA (Rivest–Shamir–Adleman) crypto-system are used to exchange a key such that no outside observer can view the exchanged key. The protocol for RSA is thus:
Alice creates a public and private key and publishes the public key in such a way that Bob has it (Alice sends it to him for example.)
Bobs message (the PRNG seed) is "locked" using Alice's public key and the locked message is sent to Alice. Only the private key can unlock the locked message.
Alice can unlock the message using her private key. Now both Alice and Bob have a shared secret (the seed to the PRNG.)
All remaining communication is done using a stream or block cipher that has been initialized to the same seed on both ends.
#include <stdio.h> #include <stdlib.h> // A stream cipher in "3" lines of code: int main(int argc, char *argv[]) { int ch; if (argc < 2) { fprintf(stderr,"Usage: cipher <key>\n"); exit(0); } srand(atoi(argv[1])); // srand() seeds the PRNG using the key value. while((ch = getchar()) != EOF) putchar(ch ^ (rand() & 0xFF)); return 0; }
Note: The above is a terrible stream cipher, as it uses a pretty low end PRNG. Cryptographically secure PRNGs (stream ciphers) should have the following characteristics:
/** * Some functions that would demonstrate how one might use a stream cipher at a * very low level. You probably shouldn't use these, unless you replace rand() * with a cryptographically secure PRNG. */ void combobulate(uint8_t *buf, size_t length) { for(int i=0; i < length; i++) buf[i] ^= (rand()&0xFF); } ssize_t enc_read(int fd, void *buf, size_t size) { int r = read(fd, buf, size); if (r > 0) combubulate(buf, r); return r; } ssize_t enc_write(int fd, void *buf, size_t size) { combubulate(buf, size); return write(fd, buf, size); } /** * For multiple connections. Each connection would have it's own random state * saved as a pointer to rstate, which can be passed to the PRNG, in this case * the re-entrant version of rand(), rand_r(). */ void combobulate(int *rstate, uint8_t *buf, size_t length) { for(int i=0; i < length; i++) buf[i] ^= (rand_r(rstate)&0xFF); }
Terms:
A certificate is a certificate of identity for a host that contains among other information:
The signature is the above information (a-e) encrypted using the issuers private key which may then be deciphered only using the issuers pubic key then verified with the above.
If we trust the certificate issuer, then we after we verify the certificate is legitimate by using the issuers public key (usually already pre-installed in our web-browsers or operating systems,) then we can trust that the host we're communicating with is the actual owner of the certificate and we can begin key-exchange with it using the public key found in the certificate.