Skip to content

Commit

Permalink
Initial commit with first working version
Browse files Browse the repository at this point in the history
  • Loading branch information
sadovnychyi committed Aug 13, 2016
1 parent fc2672f commit ecad6a1
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 91 deletions.
93 changes: 4 additions & 89 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,89 +1,4 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# IPython Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# dotenv
.env

# virtualenv
venv/
ENV/

# Spyder project settings
.spyderproject

# Rope project settings
.ropeproject
Crypto
fuzzywuzzy
*.dist-info
*.egg-info
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build:
pip install --requirement requirements.txt --target=.

link:
@ln -fs `pwd` "/Users/sadovnychyi/Google Drive/Sync/Alfred.alfredpreferences/workflows"
41 changes: 39 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,39 @@
# alfred-chrome-passwords
Browse your passwords saved in Google Chrome using Alfred
# Alfred Chrome Passwords

Browse your passwords saved in Google Chrome using Alfred instead of https://password.google.com.

# Demo

![sample](https://cloud.githubusercontent.com/assets/193864/17643019/7d16b81c-618f-11e6-824a-784b678a4f50.gif)

# Installation

See [releases](https://github.com/sadovnychyi/alfred-chrome-passwords/releases)
to download latest build. Simply open it with Alfred and follow instructions.

# Usage

Type `password` keyword to see the most frequent passwords you have used.
Continue typing to filter them and press `Enter` to copy it. Note that by
default you will be required to type your OS password to decrypt selected
password.

# Using non-default profile

Provide `--profile` argument to `passwords.py` script. For example:
```bash
python passwords.py --query="$1" --profile="Profile 1"
```
![image](https://cloud.githubusercontent.com/assets/193864/17643119/9ac8a2d2-6192-11e6-9763-b53ad6769a1c.png)

# Security

Chrome stores all your passwords inside of sqlite database named `Login Data`.
All passwords are securely encrypted by a master password which is stored in
your keychain. By default you have to use your OS password to access Chrome's
safe storage password. If you feel super safe allowing anybody who has physical
access to your Mac to access your passwords – you can remove that confirmation
window. To do so, open `Keychain Access`, search for `Chrome Safe Storage`, go
to `Access Control` and select `Allow all applications to access this item`. But... just don't!

![image](https://cloud.githubusercontent.com/assets/193864/17643070/0f400788-6191-11e6-9c6e-02a0d411a05c.png)
18 changes: 18 additions & 0 deletions decrypt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from Crypto.Cipher import AES
import hashlib
import subprocess
import sys


def main(password):
master_password = subprocess.check_output([
'/usr/bin/security', 'find-generic-password', '-w', '-s',
'Chrome Safe Storage', '-a', 'Chrome']).strip()
key = hashlib.pbkdf2_hmac(hash_name='sha1', password=master_password,
salt=b'saltysalt', iterations=1003, dklen=16)
cipher = AES.new(key, AES.MODE_CBC, IV=b' ' * 16)
return sys.stdout.write(cipher.decrypt(password.decode('base64')))


if __name__ == '__main__':
main(sys.argv[1] if len(sys.argv) > 1 else '')
Binary file added icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
163 changes: 163 additions & 0 deletions info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>bundleid</key>
<string>com.github.sadovnychyi.chromepasswords</string>
<key>category</key>
<string>Productivity</string>
<key>connections</key>
<dict>
<key>049FB103-A748-4A6E-9B25-3CE43041BD8D</key>
<array>
<dict>
<key>destinationuid</key>
<string>9F1468CE-D107-4676-BC98-B66947DB9AE3</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>9F1468CE-D107-4676-BC98-B66947DB9AE3</key>
<array>
<dict>
<key>destinationuid</key>
<string>49F46A2B-E033-4214-9CC6-D84213949876</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
</dict>
<key>createdby</key>
<string>Dmytro Sadovnychyi</string>
<key>description</key>
<string>Browse your passwords saved in Google Chrome</string>
<key>disabled</key>
<false/>
<key>name</key>
<string>Chrome Passwords</string>
<key>objects</key>
<array>
<dict>
<key>config</key>
<dict>
<key>alfredfiltersresults</key>
<false/>
<key>argumenttype</key>
<integer>1</integer>
<key>escaping</key>
<integer>102</integer>
<key>keyword</key>
<string>password</string>
<key>queuedelaycustom</key>
<integer>3</integer>
<key>queuedelayimmediatelyinitially</key>
<true/>
<key>queuedelaymode</key>
<integer>0</integer>
<key>queuemode</key>
<integer>1</integer>
<key>runningsubtext</key>
<string>Reading database...</string>
<key>script</key>
<string>python passwords.py --query="$1"</string>
<key>scriptargtype</key>
<integer>1</integer>
<key>scriptfile</key>
<string></string>
<key>subtext</key>
<string>Browse your passwords saved in Google Chrome</string>
<key>title</key>
<string>Chrome Passwords</string>
<key>type</key>
<integer>0</integer>
<key>withspace</key>
<true/>
</dict>
<key>type</key>
<string>alfred.workflow.input.scriptfilter</string>
<key>uid</key>
<string>049FB103-A748-4A6E-9B25-3CE43041BD8D</string>
<key>version</key>
<integer>2</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>autopaste</key>
<false/>
<key>clipboardtext</key>
<string>{query}</string>
<key>transient</key>
<false/>
</dict>
<key>type</key>
<string>alfred.workflow.output.clipboard</string>
<key>uid</key>
<string>49F46A2B-E033-4214-9CC6-D84213949876</string>
<key>version</key>
<integer>2</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>concurrently</key>
<false/>
<key>escaping</key>
<integer>102</integer>
<key>script</key>
<string>python decrypt.py $1</string>
<key>scriptargtype</key>
<integer>1</integer>
<key>scriptfile</key>
<string></string>
<key>type</key>
<integer>0</integer>
</dict>
<key>type</key>
<string>alfred.workflow.action.script</string>
<key>uid</key>
<string>9F1468CE-D107-4676-BC98-B66947DB9AE3</string>
<key>version</key>
<integer>2</integer>
</dict>
</array>
<key>readme</key>
<string>Browse your passwords saved in Google Chrome.</string>
<key>uidata</key>
<dict>
<key>049FB103-A748-4A6E-9B25-3CE43041BD8D</key>
<dict>
<key>xpos</key>
<integer>120</integer>
<key>ypos</key>
<integer>290</integer>
</dict>
<key>49F46A2B-E033-4214-9CC6-D84213949876</key>
<dict>
<key>xpos</key>
<integer>730</integer>
<key>ypos</key>
<integer>290</integer>
</dict>
<key>9F1468CE-D107-4676-BC98-B66947DB9AE3</key>
<dict>
<key>xpos</key>
<integer>510</integer>
<key>ypos</key>
<integer>290</integer>
</dict>
</dict>
<key>version</key>
<string>1.0.0</string>
<key>webaddress</key>
<string>https://github.com/sadovnychyi/alfred-chrome-passwords</string>
</dict>
</plist>
56 changes: 56 additions & 0 deletions passwords.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import argparse
import base64
import codecs
import fuzzywuzzy.process
import json
import os
import sqlite3
import sys
import tempfile
try:
from urlparse import urlparse
except ImportError:
from urllib.parse import urlparse

HOME = os.path.expanduser('~')
CHROME = 'Library/Application Support/Google/Chrome'
PROFILE = 'Default'
LOGIN_DATA = 'Login Data'


def main(query, profile):
with tempfile.NamedTemporaryFile() as tmp:
with open(os.path.join(HOME, CHROME, profile, LOGIN_DATA), 'rb') as f:
tmp.write(f.read())
cursor = sqlite3.connect(tmp.name).cursor()
cursor.execute('''SELECT origin_url, username_value, password_value
FROM logins ORDER BY times_used desc''')
passwords = []
for origin_url, account, password in cursor.fetchall():
password = base64.b64encode(password[3:]).decode('utf8')
url = urlparse(origin_url)
title = codecs.decode(url.netloc.encode('utf8'), 'idna')
if title.lower().startswith('www.'):
title = title[4:]
if url.scheme == 'android':
title = '%s://%s' % (url.scheme, title.split('@')[1])
passwords.append({
'type': 'default',
'title': title,
'subtitle': account,
'arg': password,
'valid': 'true' if len(password) > 0 else 'false',
'autocomplete': title,
})
passwords = fuzzywuzzy.process.extractBests(
query, passwords, processor=lambda x: '%s %s' % (x['title'], x['subtitle']))
json.dump({'items': [p[0] for p in passwords]}, sys.stdout, indent=2)
sys.stdout.flush()


if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--query', default='')
parser.add_argument('--profile', default=PROFILE)
args = parser.parse_args()
main(args.query, args.profile)
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pycrypto
fuzzywuzzy

0 comments on commit ecad6a1

Please sign in to comment.