This program can be used by anonymous contributors to release partial information about their identity - they can show that they are someone from a list of PGP key holders, without revealing which member of the list they are. Maybe it can help in the recent controvery over the identity of anonymous posters. It's a fairly low-level program that should be wrapped in a nicer UI. I'll send a couple of perl scripts later that make it easier to use. === /* Implementation of ring signatures from * http://theory.lcs.mit.edu/~rivest/RivestShamirTauman-HowToLeakASecret.pdf * by Rivest, Shamir and Tauman * * This creates and verifies a signature such that it was produced from * one of a fixed set of RSA keys. * * It requires the openssl library to build, which is available from * www.openssl.org. * * This program takes a PGP public key ring file which holds a set of * old-style RSA public keys. It creates and verifies signatures which * are such that they were issued by one of the keys in that file, but * there is no way to tell which one did it. In this way the signer can * leak partial information about his identity - that he is one member * of a selected set of signers. * * To sign, the signer must also give a PGP secret key file which holds * one key (actually the program ignores any keys past the first). * That key should be the secret part of one of the keys in the public * key file. Also, it should be set to have no passphrase - it is too * complicated for a simple program like this to try to untangle PGP * passphrases. So set your key to have no passphrase, then run this * program, then set it back. * * The program outputs the signature in the form of a list of big numbers, * base64 encoded. There will be as many numbers as there were keys in * the public key file. So signatures are quite large in this scheme, * proportional to the number of keys in the group that the signature * comes from. They are also proportional to the largest key in the * group, so all else being equal try not to include really big keys if * you care about size. * * The signature is not appended to the text being signed, it is just * output separately. The signer can combine them manually with some kind * of cut marks so that the recipient can separate out the signature from * the file being signed. Some perl scripts that do this are supposed * to be distributed with the program. (That is what is used to verify * the signature in this file itself.) * * The recipient must use the same PGP public key file that the signer * used. So that may have to be sent along as well. He runs the program * with the PGP file and the file to be verified, and sends the signature * data into stdin (using the "<" character). The program will print * whether the signature is good or not. * * This program was written in just a couple of evenings so it is * a little rough. This is version 0.9 or so - at least it works. * It has only been tested on my Linux system. * * The program is released into the public domain. See the end for * authorship information. */ #include <stdio.h> #include <stdlib.h> #include "openssl/bn.h" #include "openssl/rsa.h" #include "openssl/sha.h" #include "openssl/evp.h" /* Cipher block size; we use Blowfish */ #define CIPHERBLOCK 8 typedef unsigned char uchar; enum { ERR_OK = 0, ERR_BADPKT=-100, ERR_EOF, ERR_SECNOTFOUND, ERR_BADSIG, }; /************************** PGP FILE PARSING ***************************/ /* Read the N and E values from a PGP public key packet */ int rdpgppub( BIGNUM *n, BIGNUM *e, unsigned *bytesused, uchar *buf, unsigned len ) { int nbits, nlen, ebits, elen; unsigned o=2; if (len < 10) return ERR_BADPKT; if (buf[0] == 4) /* Check version 4, 3, or 2 */ o = 0; else if (buf[0] != 2 && buf[0] != 3) /* V2&3 have 2 extra bytes */ return ERR_BADPKT; if (buf[5+o] != 1) /* Check alg - 1 is RSA */ return ERR_BADPKT; nbits = (buf[6+o] << 8) | buf[7+o]; /* Read modulus */ nlen = (nbits + 7)/8; if (len < 10+o+nlen) return ERR_BADPKT; BN_bin2bn(buf+o+8, nlen, n); ebits = (buf[8+o+nlen] << 8) | buf[9+o+nlen]; /* Read exponent */ elen = (ebits + 7)/8; if (len < 10+o+nlen+elen) return ERR_BADPKT; BN_bin2bn(buf+10+o+nlen, elen, e); if (bytesused) *bytesused = 10+o+nlen+elen; return ERR_OK; } /* Read the N, E, D values from a PGP secret key packet with no passphrase */ int rdpgpsec( BIGNUM *n, BIGNUM *e, BIGNUM *d, uchar *buf, unsigned len ) { int err; int nbits, nlen, ebits, elen, dbits, dlen; unsigned o; if ((err = rdpgppub(n, e, &o, buf, len)) < 0) return err; if (len < o+3) return ERR_BADPKT; if (buf[o] != 0) /* Check that packet is unencrypted */ return ERR_BADPKT; dbits = (buf[o+1] << 8) | buf[o+2]; /* Read private exponent */ dlen = (dbits + 7)/8; if (len < o+3+dlen) return ERR_BADPKT; BN_bin2bn(buf+o+3, dlen, d); return ERR_OK; } /* Read the next PGP packet into malloc'd memory */ int getpgppkt( uchar **pbuf, unsigned *plen, int *type, FILE *f ) { int c, c1; uchar *buf; unsigned len; int llen; uchar lbuf[4]; c = fgetc(f); if (c == EOF) return ERR_EOF; if ((c & 0xc0) == 0x80) { /* Old PGP packet */ *type = (c >> 2) & 0xf; llen = c & 0x3; if (llen++==3) return ERR_BADPKT; rdllen: if (llen==3) llen=4; memset (lbuf, 0, 4); if (fread(lbuf+4-llen, 1, llen, f) != llen) return ERR_BADPKT; len = (lbuf[0]<<24) | (lbuf[1]<<16) | (lbuf[2]<<8) | lbuf[3]; } else if ((c & 0xc0) == 0xc0) { /* New PGP packet */ *type = c & 0x3f; c = fgetc(f); if (c == EOF) return ERR_BADPKT; if (c == 0xff) { llen = 4; goto rdllen; } if (c >= 0xe0) return ERR_BADPKT; if (c >= 0xc0) { /* Two byte length */ c1 = fgetc(f); if (c1 == EOF) return ERR_BADPKT; len = (c<<8) + c1 - 0xc000 + 0xc0; } else { /* One byte length */ len = c; } } else { /* Non PGP packet */ return ERR_BADPKT; } buf = malloc(len); if (buf==NULL) return ERR_BADPKT; if (fread(buf, 1, len, f) != len) return ERR_BADPKT; *pbuf = buf; *plen = len; return ERR_OK; } /* Read a PGP key ring and create arrays of all the n, e values */ int rdpgppubring(BIGNUM ***n_arr_ptr, BIGNUM ***e_arr_ptr, int *nkeys_ptr, FILE *f) { int err = ERR_OK; uchar *buf; unsigned len; int type; int nkeys = 0; BIGNUM **n_arr = NULL; BIGNUM **e_arr = NULL; BIGNUM *n, *e; n_arr = malloc(sizeof(BIGNUM *)); e_arr = malloc(sizeof(BIGNUM *)); while (err == ERR_OK) { err = getpgppkt (&buf, &len, &type, f); if (err != ERR_OK) break; if (type == 6) /* public key packet */ { n = BN_new(); e = BN_new(); err = rdpgppub(n, e, NULL, buf, len); if (err != ERR_OK) break; ++nkeys; n_arr = realloc(n_arr, sizeof(BIGNUM *) * nkeys); e_arr = realloc(e_arr, sizeof(BIGNUM *) * nkeys); n_arr[nkeys-1] = n; e_arr[nkeys-1] = e; } free (buf); } if (err != ERR_EOF) return err; err = ERR_OK; *n_arr_ptr = n_arr; *e_arr_ptr = e_arr; *nkeys_ptr = nkeys; return err; } /* Read a PGP secret key file and find the corresponding value in the * array of public key values. Return the d value and the index in the * public key array. */ int rdpgpsecring(BIGNUM *d, int *secindex, BIGNUM **n_arr, BIGNUM **e_arr, int nkeys, FILE *f) { int err = ERR_OK; BIGNUM *n, *e; uchar *buf; unsigned len; int i; int type; err = getpgppkt (&buf, &len, &type, f); if (err != ERR_OK) return err; if (type != 5) /* Secret key packet */ return ERR_BADPKT; n = BN_new(); e = BN_new(); err = rdpgpsec(n, e, d, buf, len); if (err != ERR_OK) return err; for (i=0; i<nkeys; i++) /* Find corresponding public key */ { if (BN_cmp (n, n_arr[i]) == 0 && BN_cmp (e, e_arr[i]) == 0) break; } if (i == nkeys) return ERR_SECNOTFOUND; *secindex = i; return ERR_OK; } /************************** UTILITY FUNCTIONS ***************************/ /* Sort the n and e arrays by increasing n */ int sortkeys (BIGNUM **n, BIGNUM **e, int nkeys) { int i, j; BIGNUM *t; /* Do a bubble sort as the number of keys is not usually very large */ for (i=0; i<nkeys-1; i++) { for (j=i+1; j<nkeys; j++) { if (BN_cmp( n[i], n[j] ) > 0) { t = n[i]; n[i] = n[j]; n[j] = t; t = e[i]; e[i] = e[j]; e[j] = t; } } } return ERR_OK; } /* Hash the file. Should have opened it in ASCII mode so that we have * standard Unix line endings (newlines only). */ int hashfile( uchar md[5], FILE *f ) { char buf[1024]; SHA_CTX sha; SHA_Init(&sha); for ( ; ; ) { if (fgets (buf, sizeof(buf), f) == NULL) break; SHA_Update(&sha, buf, strlen(buf)); } SHA_Final (md, &sha); return ERR_OK; } /* Do an RSA enc/dec, where the input/output value may be larger * than n. In fact, val should be much larger than n or this may fail * to keep val within the desired range. * To decrypt pass d in place of e. */ int rsaencdec( BIGNUM *rslt, BIGNUM *val, BIGNUM *n, BIGNUM *e, BN_CTX *ctx ) { BIGNUM *rem = BN_new(); BIGNUM *newrem = BN_new(); BN_div (NULL, rem, val, n, ctx); BN_mod_exp (newrem, rem, e, n, ctx); BN_sub (rslt, val, rem); BN_add (rslt, rslt, newrem); BN_free (rem); BN_free (newrem); } /************************** SIG CREATE/VERIFY ***************************/ /* Verify a signature. sigs holds the per-key signature values, * hashval is the hash of the data which was signed, valbytes is the * length of the values we work with, several bytes longer than the longest * modulus, and n_arr and e_arr are the RSA public key values, of whic * there are nkeys of them. (There are also nkeys of sigs.) */ int checksig( BIGNUM **sigs, uchar *hashval, int hashvalbytes, int valbytes, BIGNUM **n_arr, BIGNUM **e_arr, int nkeys, BN_CTX *ctx ) { BIGNUM *val = BN_new(); uchar ivec[CIPHERBLOCK]; BF_KEY bf; uchar *sigbuf; uchar *xorbuf; int vallen; int i, j; /* Key cipher with the hash value */ BF_set_key (&bf, hashvalbytes, hashval); /* Init xorbuf to 0's */ xorbuf = malloc(valbytes); memset (xorbuf, 0, valbytes); sigbuf = malloc(valbytes); for (i=0; i<nkeys; i++) { rsaencdec (val, sigs[i], n_arr[i], e_arr[i], ctx); vallen = BN_num_bytes(val); if (vallen > valbytes) { fprintf (stderr, "Bad signature created by signer\n"); exit (3); } /* XOR into buffer */ memset (sigbuf, 0, valbytes); BN_bn2bin (val, sigbuf+valbytes-vallen); for (j=0; j<valbytes; j++) xorbuf[j] ^= sigbuf[j]; memset (ivec, 0, sizeof(ivec)); BF_cbc_encrypt (xorbuf, xorbuf, valbytes, &bf, ivec, BF_ENCRYPT); } BN_free (val); /* xorbuf should be all 0's for the sig to verify */ for (j=0; j<valbytes; j++) if (xorbuf[j] != 0) break; free (xorbuf); free (sigbuf); if (j < valbytes) return ERR_BADSIG; return ERR_OK; } /* Create a signature. psigarray is the returned array of bignums, * of which there are nkeys. hashval is the hash of the data being * signed. d is the RSA private key value for the key at position * secindex. valbytes is the size of the values we are working with, * several bytes greater than the length of the longest modulus. * And n_arr and e_arr are the RSA public key values, of which there * are nkeys. */ int createsig( BIGNUM ***psigarray, uchar *hashval, int hashvalbytes, BIGNUM *d, int secindex, int valbytes, BIGNUM **n_arr, BIGNUM **e_arr, int nkeys, BN_CTX *ctx ) { BIGNUM *val = BN_new(); BIGNUM *bigval = BN_new(); BIGNUM **sigs; BF_KEY bf; uchar ivec[CIPHERBLOCK]; uchar *sigbuf; uchar *lxorbuf; uchar *rxorbuf; int vallen; int i, j; /* Key cipher with the hash value */ BF_set_key (&bf, hashvalbytes, hashval); BN_lshift (bigval, BN_value_one(), valbytes*8); /* Init two xorbufs we will make meet in the middle */ rxorbuf = malloc(valbytes); memset (rxorbuf, 0, valbytes); lxorbuf = malloc(valbytes); memset (lxorbuf, 0, valbytes); memset (ivec, 0, sizeof(ivec)); /* Start the MITM process on the left xorbuf */ BF_cbc_encrypt (lxorbuf, lxorbuf, valbytes, &bf, ivec, BF_DECRYPT); sigbuf = malloc(valbytes); sigs = (BIGNUM **)malloc(nkeys * sizeof(BIGNUM *)); for (i=0; i<secindex; i++) { /* For other keys do a fake value */ sigs[i] = BN_new(); BN_rand_range (sigs[i], bigval); rsaencdec (val, sigs[i], n_arr[i], e_arr[i], ctx); /* Infinitisimal chance that vallen > valbytes with random val */ vallen = BN_num_bytes(val); /* XOR into right xor buf and encrypt */ memset (sigbuf, 0, valbytes); BN_bn2bin (val, sigbuf+valbytes-vallen); for (j=0; j<valbytes; j++) rxorbuf[j] ^= sigbuf[j]; memset (ivec, 0, sizeof(ivec)); BF_cbc_encrypt (rxorbuf, rxorbuf, valbytes, &bf, ivec, BF_ENCRYPT); } for (i=nkeys-1; i>secindex; i--) { /* For other keys do a fake value */ sigs[i] = BN_new(); BN_rand_range (sigs[i], bigval); rsaencdec (val, sigs[i], n_arr[i], e_arr[i], ctx); /* Infinitisimal chance that vallen > valbytes with random val */ vallen = BN_num_bytes(val); /* XOR into left xor buf and decrypt */ memset (sigbuf, 0, valbytes); BN_bn2bin (val, sigbuf+valbytes-vallen); for (j=0; j<valbytes; j++) lxorbuf[j] ^= sigbuf[j]; memset (ivec, 0, sizeof(ivec)); BF_cbc_encrypt (lxorbuf, lxorbuf, valbytes, &bf, ivec, BF_DECRYPT); } /* XOR the two buffers to get the value we must RSA sign */ for (j=0; j<valbytes; j++) lxorbuf[j] ^= rxorbuf[j]; /* Get val as the value we need to do the RSA signature to */ sigs[secindex] = BN_new(); BN_bin2bn (lxorbuf, valbytes, val); rsaencdec (sigs[secindex], val, n_arr[secindex], d, ctx); BN_free (val); BN_free (bigval); free (sigbuf); free (lxorbuf); free (rxorbuf); *psigarray = sigs; return ERR_OK; } /*************************** USER INTERFACE ****************************/ static char *prog; void userr() { fprintf (stderr, "Usage:\n" "To sign (signature data to stdout):\n" " %s -s textfile pubkeyfile seckeyfile\n" "To verify a signature (signature data from stdin):\n" " %s -v textfile pubkeyfile\n", prog, prog); exit (1); } int main (int ac, char **av) { FILE *fpub, *fsec, *fdata; BN_CTX *ctx; BIGNUM *d; BIGNUM **n_arr; BIGNUM **e_arr; BIGNUM **sigarray; uchar md[SHA_DIGEST_LENGTH]; int secindex; int valbytes; int nkeys; int i; int dosign; int err; ctx = BN_CTX_new(); d = BN_new(); prog = av[0]; if (ac < 2) userr(); if (strcmp( av[1], "-s" ) == 0) dosign = 1; else if (strcmp( av[1], "-v" ) == 0) dosign = 0; else userr(); if (ac < (dosign ? 5 : 4) ) userr(); fdata = fopen (av[2], "r"); /* text mode */ if (fdata==NULL) userr(); fpub = fopen (av[3], "rb"); if (fpub==NULL) userr(); if (dosign) { fsec = fopen (av[4], "rb"); if (fsec==NULL) userr(); } err = rdpgppubring(&n_arr, &e_arr, &nkeys, fpub); if (err != ERR_OK) goto error; fclose (fpub); sortkeys (n_arr, e_arr, nkeys); if (dosign) { err = rdpgpsecring(d, &secindex, n_arr, e_arr, nkeys, fsec); if (err != ERR_OK) goto error; fclose (fsec); } /* For our values we use 2^128 times the largest n; mult of CIPHERBLOCK */ valbytes = BN_num_bytes (n_arr[nkeys-1]) + 16; valbytes = ((valbytes+CIPHERBLOCK-1)/CIPHERBLOCK)*CIPHERBLOCK; /* Hash the file to sign/verify */ err = hashfile (md, fdata); if (dosign) { BIO *bio, *b64; uchar *sigbuf; err = createsig (&sigarray, md, sizeof(md), d, secindex, valbytes, n_arr, e_arr, nkeys, ctx); if (err != ERR_OK) goto error; b64 = BIO_new(BIO_f_base64()); bio = BIO_new_fp(stdout, BIO_NOCLOSE); bio = BIO_push(b64, bio); sigbuf = (uchar *)malloc(valbytes); for (i=0; i<nkeys; i++) { memset (sigbuf, 0, valbytes); BN_bn2bin(sigarray[i], sigbuf+valbytes-BN_num_bytes(sigarray[i])); BIO_write(bio, sigbuf, valbytes); } BIO_flush(bio); BIO_free_all(bio); free (sigbuf); } else { /* Read sig data from stdin */ BIO *bio, *b64; uchar *sigbuf; int inlen; b64 = BIO_new(BIO_f_base64()); bio = BIO_new_fp(stdin, BIO_NOCLOSE); bio = BIO_push(b64, bio); sigarray = (BIGNUM **)malloc(nkeys * sizeof(BIGNUM *)); sigbuf = (uchar *)malloc(valbytes); for (i=0; i<nkeys; i++) { if ((inlen = BIO_read(bio, sigbuf, valbytes)) < valbytes) userr(); sigarray[i] = BN_bin2bn (sigbuf, valbytes, NULL); } free (sigbuf); BIO_free_all(bio); err = checksig (sigarray, md, sizeof(md), valbytes, n_arr, e_arr, nkeys, ctx); if (err == ERR_OK) { printf ("Good signature\n"); } else if (err = ERR_BADSIG) { printf ("ERROR: Bad signature\n"); exit (2); } else { goto error; } } error: if (err != ERR_OK) { fprintf (stderr, "Error %d\n", err); exit (1); } exit (0); } /* Signature block! Who wrote this program? Someone from the keys below. -----BEGIN PGP PUBLIC KEY BLOCK----- Version: 2.6.2 mQCNAisbwocAAAED/jitkUnDwPdZk1Rdr0YNT7h7OHd9zbJbTZEQgdEs5LxQ1h4t S+fCbgM6zxfMvCWqWPendS3ikEStq1PMGQKtt0b0/4BafB6dO1ljSBp+UWkw86FL BmCcQkED0CmQOFAEy2p2SURvdO4tExerfUgf+oYQg99v03mvYlVkk3dax7hlAAUR tBBsb2tpQG9ic2N1cmEuY29ttChMYW5jZSBNLiBDb3R0cmVsbCA8bG9raUBuYXRl bHkudWNzZC5lZHU+tDhMYW5jZSBNLiBDb3R0cmVsbCAoYXQgaG9tZSkgPGxjb3R0 cmVsbEBwb3BtYWlsLnVjc2QuZWR1PpkAjQMxav4LAAABBADJkZ8ZP83waDvuf/Lx 4UB7KFSkm9IKKht2FpW5ztHG22EmCuarCYhSB+R7dzIAN4Wx9UcdS3x724ozuoNm bQboiSsSKO41efGcn5zbzFKlLU+Im5utXJ1QtYlbjx6PjnfZiajVAIpgwiekwP7I PxL6V0i4jYlln3dGUYkxK0j29QAFEbQjSWFuIEdvbGRiZXJnIDxpYW5nQGNzLmJl cmtlbGV5LmVkdT60IUlhbiBHb2xkYmVyZyA8aWFuQGN5cGhlcnB1bmtzLmNhPrQk SWFuIEdvbGRiZXJnIDxpYW5AemVyb2tub3dsZWRnZS5jb20+mQCNAi0xHTIAAAEE AKeIU9S010e1AxYy2R379ptHunqM0kRMgWnOwfCnVets8jThr7B87pzFNVj6kBs8 F9TKQdk62JR5Kiq2rVODFSLmN2JThnhfDu/tAYAz8fJsWkxGn5IhcjxkQpfb2LDs 4EBJgWhI9HxIfCvhSkFdrFe9JBfm0KKB5sGoFIWXVYodAAUTtCFQcjBkdWN0IEN5 cGhlciA8YWx0LnNlY3VyaXR5LnBncD6ZAI0CL7SNAQAAAQQAruhfQV42dxod9rW/ 5XtIxS+ICbyoHyitYbTO+Nrhm1KsdWD88zCmlSHji3kRED2WW1EF0e7fT5hKHxrV 1i3Vc4PzeeSfOP2uG2kUA+rLLCUtWE1Vjc9UvIxtQvDbtoQAZz5K0WqsOhRDT6P1 BT9gf8jJU32sFonKGwgMRScZrzUABRG0KEJlbiBMYXVyaWUgPGJlbkBnb256by5i ZW4uYWxncm91cC5jby51az60HEJlbiBMYXVyaWUgPGJlbkBjcnlwdGl4Lm9yZz60 HkJlbiBMYXVyaWUgPGJlbkBhbGdyb3VwLmNvLnVrPpkAjQIqrDZMAAABBADCljOd puRFotKHCuLtXAtjz8h0+rWr5PTwt0weVwWA3oH93bJXUaZ4yY9a/mjtPBRTvkCx CPcLQar39Tzz+Yi1+7A9riFZSR9eDD7clbY5vWSm/CTLQlu+NOOQYLRwQvckN1e7 1zVeYzOnRNqHJx+ACP6QRaxiyTyoEwOvWCFMNwAFEbQmSGFsIEZpbm5leSA8NzQw NzYuMTA0MUBjb21wdXNlcnZlLmNvbT6ZAI0DMxu7SwAAAQQApJs0Xcl4sIUngK32 4f1sQ9H7GGctVkjxynj5b3or0rhiI2u/OowQCj7VwnCdoM/Itqz6HUiVRO8D+eHz XQRpAXKoeEgxzMN45iNvgneU0/VsN49bCDCM7JPHau2cMm4xWhQUA6s59CxhR+Y2 cMWxLpwcXDJ//+YoNZtIfcgAK9EABRG0HkVyaWMgWW91bmcgPGVheUBjcnlwdHNv ZnQuY29tPpkAjQIty/OuAAABBACkXvI/UFKi+Dw0ErJYkykNMk5HSy/ARQZu3NN0 QvTpeTz/So/0bZ7HcRTE7Vu8JDME6WNYxdtx0fseeC6zqYvIoOhxJol1VVrjQckY 0jIM/I765zDNP7SDgjwIDTMYgDVFqq3rTxFgmRPbC28pZCa6zneeGSslwU8Pw+wC +7uKsQAFEbQhQ29saW4gUGx1bWIgPGNvbGluQG55eC5jcy5kdS5lZHU+ =/CJG -----END PGP PUBLIC KEY BLOCK----- ++multisig v1.0 pEsBwalpBRxWyJR8tkYm6qR27UW9IT6Vg8SlOHIsEkk04RJvoSy0cy4ISFCq6vDX 5ub6c+MYi/UoyR6tI7oqpMu1abcXWm2DkfDiCsD6jQddVkiiYdG7Bih8JWdWmp5l AgzqUoz14671/ezmWSrPNsTNKV96+ZLEanZsqfkpQcnZpLkWVpJzQFe0VgDQ64b2 +e2efrbknLFq0FTdX7Sh3qzAfzNYYgADmeOxDoTm9sb6T0fULf1P7mjiN2LZXuEW m/8QvksaQi9KGa/0xN2m0heNtS1cfsTa+NJz8XYyG/tnMy7+mvI3c3lrnz+6Dpyp pbNwaX+12VcqtfNec9faoq8RJgFxmSO/ZfMOGM8cFBQ75ZOaoBJP5ObHZ/63FFh5 Wh5GzwJjQs0vLwpM3iF6G+IixEqAQYisUdCopP1wXCLgltDM6l7jRlXxNDj0AXQ1 eQJolo32vemcy8Z8GAn5tpQHmJwpdzZpboWRQY53pD4mVnEMN4GBC1mhbbI2z+Oh lPglqmmy3p4D+psNU1rlNv6yH/L0PgcuW7taVpbopjl4HLuJdWcKHJlXish3D/jb eoQ856fYFZ/omGiO9x1D0BsnGFLZVWob4OIZRzO/Pc49VIhFy5NsV2zuozStId89 [...] */