├── auto-image.py ├── download-keyserv.py ├── GDPR.md ├── upload-text-file.py ├── upload-file.py └── README.md /auto-image.py: -------------------------------------------------------------------------------- 1 | # gpg test program 2 | # 3 | # This is just some nasty test code to add images automatically to a gpg pub key. 4 | # Uses pexpect to interact with the shell. 5 | # This does nothing alone its just an example, to use you need to write more code or 6 | # add it to one of the existing programs in this repo. 7 | 8 | import pexpect 9 | import sys 10 | import time 11 | 12 | email = "fake@mail" 13 | imagefile = '/usr/path/to/image/file.jpg' 14 | child = pexpect.spawn('gpg --edit-key ' + email) 15 | child.logfile = sys.stdout 16 | 17 | child.expect('gpg>') 18 | child.sendline('addphoto') 19 | child.expect('Enter JPEG filename for photo ID: ') 20 | child.sendline(imagefile) 21 | child.expect('y+') 22 | child.send('y\n') 23 | child.expect('Is this photo correct (y/N/q)?') 24 | child.send('y\n') 25 | child.expect('gpg> ') 26 | child.sendline('save') 27 | child.expect (pexpect.EOF) 28 | -------------------------------------------------------------------------------- /download-keyserv.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | import urllib2 4 | import base64 5 | import sys 6 | 7 | try: 8 | if "http://" not in sys.argv[1]: 9 | print "not the expected url please check your input" 10 | else: 11 | url = sys.argv[1] 12 | 13 | file_name = url[url.index("@"):url.index("&")].replace('@','') 14 | print file_name 15 | r = requests.get(url) 16 | data = r.text 17 | 18 | data_cleaned = {} 19 | for x in data.split(): 20 | if 'class="uid"' in x and "@" in x: 21 | data_cleaned[int(x.replace('/','/').replace('class="uid">','').replace('','').split('@')[0])] = x.replace('/','/').replace('class="uid">','').replace('','').split('@')[1] 22 | 23 | with open(file_name,"wb") as handle: 24 | for x in range(0,len(data_cleaned)): 25 | handle.write(base64.b64decode(data_cleaned[x])) 26 | print file_name + " has been downloaded." 27 | except: 28 | print "Something went wrong please try again, check your input or try switching your computer on and off" 29 | -------------------------------------------------------------------------------- /GDPR.md: -------------------------------------------------------------------------------- 1 | # This section points out areas that I believe to effect SKS-keyservers 2 | 3 | Anyone is welcome to contribute to this document as long as it is not baseless opinions, you must have valid points that can be backed up with sections of the GDPR or the Data Protection Act. 4 | 5 | ### All information here is taken from the GDPR its self. 6 | 7 | __Article24 (1)__ highlights the requirements for those processing personal data to have mechanisms in place to allow them to comply with the GDPR. 8 | 9 | (59) Modalities should be provided for facilitating the exercise of the data subject's rights under this Regulation, 10 | including mechanisms to request and, if applicable, obtain, free of charge, in particular, access to and rectification 11 | or erasure of personal data and the exercise of the right to object. The controller should also provide means for 12 | requests to be made electronically, especially where personal data are processed by electronic means. The 13 | controller should be obliged to respond to requests from the data subject without undue delay and at the latest 14 | within one month and to give reasons where the controller does not intend to comply with any such requests. 15 | 16 | __Article 25's__ title explaines every thing well (__Data protection by design and by default__) 17 | 18 | I think __Article 26__ points out something interesting "Joint Controllers", i think technically since all key servers sync together with new subject data and their is a collaboration with most servers in one way or another. (1) - "They shall in a transparent manner determine their respective responsibilities for compliance with the obligations under this Regulation" 19 | 20 | Currently no such mechanisms exist in current key-servers specificly the SKS key-servers. 21 | 22 | __Article 17__ (Right to eraseure('right to be forgotten')) 23 | 24 | sections 1(b)/2 - the data subject withdraws consent on which the processing is based according to point (a) of Article 6(1), or 25 | point (a) of Article 9(2), and where there is no other legal ground for the processing; 26 | 27 | 1(d) -the personal data have been unlawfully processed; 28 | 29 | __Article 7(3)__ (Conditions for consent) 30 | [The data subject shall have the right to withdraw his or her consent at any time.....It shall be as easy to withdraw as to give consent.] 31 | -------------------------------------------------------------------------------- /upload-text-file.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import random 3 | import string 4 | import sys 5 | import os 6 | 7 | 8 | #check if input is a file 9 | if os.path.exists(sys.argv[1]) != True: 10 | print "you typed something wrong, could not find that file" 11 | else: 12 | file_to_upload = sys.argv[1] 13 | if "." in sys.argv[1]: 14 | domain = sys.argv[1][sys.argv[1].index("."):] 15 | else: 16 | domain = "" 17 | 18 | keyservaddr = "add valid key server here" 19 | 20 | def parse_n_upload(upload_file): 21 | #generate random data for credentials 22 | user_name = ''.join(random.choice(string.ascii_uppercase) for _ in range(10)) 23 | email = ''.join(random.choice(string.ascii_uppercase) for _ in range(10)) + "@" +\ 24 | ''.join(random.choice(string.ascii_uppercase) for _ in range(5)) + domain 25 | 26 | passphrase = ''.join(random.choice(string.ascii_uppercase) for _ in range(5)) 27 | key_server = keyservaddr #any key server is good as it will propogate world wide 28 | 29 | #unattended key generation 30 | p=subprocess.Popen('gpg2 --batch --pinentry-mode=loopback --passphrase ' + passphrase +\ 31 | ' --quick-gen-key "' + user_name + ' ' + email + '" rsa1024', shell=True,\ 32 | stdout=subprocess.PIPE) 33 | 34 | out, err = p.communicate() 35 | 36 | #get pub key 37 | p=subprocess.Popen("gpg2 --list-key --with-colons " + email, shell=True, stdout=subprocess.PIPE) 38 | out, err = p.communicate() 39 | # parse out the key id so we can use it to send keys to the key servers 40 | key = key = [x.replace(':', '').replace('fpr', '') for x in out.split() if "fpr" in x][0] 41 | 42 | #read file and add it to the gpg key as plain text 43 | with open(upload_file, 'r') as infile: 44 | for x in infile.readlines(): 45 | if x: 46 | print x 47 | p=subprocess.Popen("gpg2 --batch --pinentry-mode=loopback --passphrase " + passphrase +\ 48 | " --quick-add-uid " + email + " " + '"' + x + '"', shell=True, stdout=subprocess.PIPE) 49 | out, err = p.communicate() 50 | print "key added" 51 | else: 52 | print "empty string ignoring" 53 | 54 | 55 | #finally send keys to a server 56 | p=subprocess.Popen("torsocks gpg2 --keyserver " + key_server + " --send-keys " +\ 57 | key, shell=True, stdout=subprocess.PIPE) 58 | out, err = p.communicate() 59 | if err: 60 | print "something went wrong with sending" 61 | 62 | #remove keys when done as they are not needed anymore 63 | p=subprocess.Popen("gpg --batch --yes --delete-secret-keys " + key +\ 64 | "&& gpg --batch --yes --delete-keys " + key, shell=True, stdout=subprocess.PIPE) 65 | 66 | if err == None: 67 | print "removing temp keys\n" 68 | print "It can take 3-10mins before your key appears on your chosen server\n" 69 | print "http://" + key_server + "/pks/lookup?search=" + email + "&op=index" 70 | else: 71 | print "something went wrong try again" 72 | 73 | parse_n_upload(file_to_upload) 74 | -------------------------------------------------------------------------------- /upload-file.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import random 3 | import string 4 | import base64 5 | import sys 6 | import os 7 | 8 | #check if input is a file 9 | if os.path.exists(sys.argv[1]) != True: 10 | print "you typed something wrong, could not find that file" 11 | else: 12 | file_to_upload = sys.argv[1] 13 | if "." in sys.argv[1]: 14 | domain = sys.argv[1][sys.argv[1].index("."):] 15 | else: 16 | domain = ".com" 17 | 18 | #generate random data for credentials 19 | user_name = ''.join(random.choice(string.ascii_uppercase) for _ in range(10)) 20 | email = ''.join(random.choice(string.ascii_uppercase) for _ in range(10)) + "@"\ 21 | + ''.join(random.choice(string.ascii_uppercase) for _ in range(5)) + domain 22 | passphrase = ''.join(random.choice(string.ascii_uppercase) for _ in range(5)) 23 | 24 | #any key server is good as it will propogate world wide 25 | key_server = "eu.pool.sks-keyservers.net" 26 | 27 | #unattended key generation 28 | p = subprocess.Popen('gpg2 --batch --pinentry-mode=loopback --passphrase ' + passphrase +\ 29 | ' --quick-gen-key "' + user_name + ' ' + email + '" rsa1024',\ 30 | shell=True, stdout=subprocess.PIPE) 31 | out, err = p.communicate() 32 | 33 | #get pub key 34 | p = subprocess.Popen('gpg2 --list-key --with-colons ' + email, shell=True, stdout=subprocess.PIPE) 35 | out, err = p.communicate() 36 | # parse out the key id so we can use it to send keys to the key servers 37 | key = key = [x.replace(':', '').replace('fpr', '') for x in out.split() if "fpr" in x][0] 38 | 39 | #open file in binary and break it up into 1305byte chunks 40 | chunk_list = [] 41 | with open(file_to_upload, 'rb') as infile: 42 | while True: 43 | chunk = infile.read(1305) 44 | if not chunk: 45 | break 46 | chunk_list.append(chunk) 47 | 48 | #encode binary chunks into base64 strings 49 | i = 0 50 | for x in chunk_list: 51 | new_uid = str(i) + "@" + base64.b64encode(x) 52 | p = subprocess.Popen("gpg2 --batch --pinentry-mode=loopback --passphrase " + passphrase\ 53 | + " --quick-add-uid " + email + " " + new_uid, shell=True, stdout=subprocess.PIPE) 54 | out, err = p.communicate() 55 | i += 1 56 | 57 | #finally send keys to a server 58 | p = subprocess.Popen("gpg2 --keyserver " + key_server + " --send-keys "\ 59 | + key, shell=True, stdout=subprocess.PIPE) 60 | out, err = p.communicate() 61 | 62 | #remove keys when done as they are not needed anymore 63 | p = subprocess.Popen("gpg --batch --yes --delete-secret-keys " + key +\ 64 | "&& gpg --batch --yes --delete-keys " + key, shell=True, stdout=subprocess.PIPE) 65 | out, err = p.communicate() 66 | 67 | if not err: 68 | print "removing temp keys\n" 69 | print "It can take 3-10mins before your key appears on your chosen server\n" 70 | print "http://" + key_server + "/pks/lookup?search=" + email + "&op=index" 71 | else: 72 | print "something went wrong try again" 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Using PGP keyservers for decentralised file storage 2 | 3 | ### This is a proof of concept for educational use only! 4 | 5 | WARNING: this may break easily and is intended for use only on linux, & only for educational purposes. 6 | 7 | So this basically works because you can have a UID(email address) that is 2048 characters in your PGP key, and from what I understand an unlimited amount of UIDs, perfect for dumping data on to the key-servers, Adding UIDs is a slow process by hand so I automated it using python, so you could dump any kind of file on the key servers. With some simple modifications you can dump plain text on to the key-servers containing any content you choose and watch it propagate through all the key-servers around the world. Once that has completed, the data is essentially impossible to be removed as said by the sks key-server Maintainer himself [Kristian Fiskerstrand](https://blog.sumptuouscapital.com/2016/03/openpgp-certificates-can-not-be-deleted-from-keyservers/). 8 | 9 | I wrote this because I think this characteristic of key-servers is actually dangerous, for example someone could upload leaked data and it would be spread around the world and accessible by anyone and unstoppable, how would this situation be dealt with? 10 | 11 | ### Which Parts of the GDPR this might be affected by: 12 | 13 | I would be happy for people to make pull requests to add information they think is relevant to this. 14 | 15 | You can also join in the discussion in the comments of an [article](https://medium.com/@mdrahony/are-pgp-key-servers-breaking-the-law-under-the-gdpr-a81ddd709d3e) I wrote to accompany this project. 16 | 17 | I have moved this section to a separate document [GDPR and its effects](https://github.com/yakamok/keyserver-fs/blob/master/GDPR.md) 18 | 19 | ### Usage 20 | 21 | __Notice:__ This Program is very slow to add data to the gpg pubkey so don't plan on super large files. 22 | 23 | ### upload-text-file.py 24 | 25 | Usage: python upload-text-file.py plain-text-file.txt 26 | One thing to note, is the text will not appear ordered, so don't expect a long rant to appear as you want it to. 27 | Also character length of each line should not exceed 2000 or the key will break and be unusable. 28 | 29 | ### upload-file.py 30 | 31 | Usage: python upload-file.py 32 | Data can take between 3-10mins before it appears on the server so don't be surprised if the link you're given does not work straight away. 33 | Requirements: gnupg2, pinentry 34 | 35 | ### download-keyserv.py 36 | 37 | Usage: python download-keyserv.py "http://eu.pool.sks-keyservers.net/pks/lookup?search=WCNGKCCWBE@UMKVS.jpg&op=index" 38 | Requirements: python-bs4 39 | 40 | The first of the uid(email) is numeric to stand for the order of the base64 string so we can be put it together again in the correct order, then the second part is simply a set chunk of binary data converted to base64. 41 | 42 | First of all had to test how many chars could be put in the uid, turns out after some testing just a little over 2040. Once you enter more than this the key becomes invalid and you have to reset your pubkey. Through some trial and error decided to stick with a safe number 1741 chars long. Once you split the binary data into 1305Byte chunks and convert it to base64 it comes to 1741 chars in length. 43 | 44 | Key deletion was added after upload is completed as the keys are no longer needed. 45 | 46 | ### Test file 47 | 48 | For those who would like to test already uploaded data, i have placed a test file here: 49 | http://eu.pool.sks-keyservers.net/pks/lookup?search=WCNGKCCWBE@UMKVS.jpg&op=index 50 | 51 | ### unpublished 52 | 53 | I wrote a version of this using OpenMPI to see what kind of scale this could be used on, it's very simple to implement and would allow a user to upload incredible amounts of data to all the key-servers. 54 | 55 | In theory it would be possible with the use of proxies and possibly tor to continually upload all kinds of illegal or sensitive data 24hrs a day accross all key-servers making it impossible to control or remove this data. 56 | 57 | This is just a proof of concept and a discussion on the potential problems of key-servers in their current form! 58 | 59 | DO NOT USE THIS TO DO ANYTHING ILLEGAL 60 | 61 | ### Notes 62 | 63 | Why did i not use GPGME? 64 | Simply because it has some kind of memory leak which is only noticable when submitting 100s of UIDs into a PGP key, then it crashes after all memory has been eaten up. I do not know if this has been fixed in recent issues if it has then its possible to write the data to the PGP key much faster than the above python code is currently able to. 65 | --------------------------------------------------------------------------------