# sillypass

Ideally, services would allow you to choose any arbitrary string of characters as your password. Unfortunately, though, some impose silly restrictions such as requiring at least one letter, one number, and one symbol, where the symbol must be one of `!@#$^`. Or they limit you to a small number of characters, such as 12 or even 8. `sillypass` is a Python package and command-line tool that generates random passwords meeting these silly restrictions.

## Installation

To run `sillypass` as a command-line script, the recommended installation method is through [pipx](https://github.com/pipxproject/pipx): `pipx install sillypass`.

To use `sillypass` as a package in your project, install it to your virtual environment with `pip install sillypass`. It will also be available as a command-line script in your virtual environment.

## Usage from the command line

You can run `sillypass` without arguments to produce a decent general-purpose password. Or you can provide arguments to change the password length or require specific character groups (such as digits) to appear a minimum number of times. Run `sillypass --help` for a description of all available arguments.

### Examples

With no arguments, `sillypass` generates a password containing 24 characters chosen randomly from ASCII letters, digits, and punctuation characters. The resulting password is printed to standard output. For example:

```
$ sillypass
<O^Ljc+[9#-a{(Lm-&c`;flZ
```

Does your service require fewer characters?

```
$ sillypass --length 12
UX\k#LMpqJDR
```

Does your service require at least one uppercase letter, one lowercase letter, and one digit?

```
$ sillypass --min-upper 1 --min-lower 1 --min-digits 1
4f5NUp]1Ih=86*+,$g77F.Yj
```

Does your service require at least one symbol, but the symbol is limited to a small set of characters?

```
$ sillypass --min-symbols 1 --symbols "@#$%_"
6I_@vyFs6mdK6UBv0IY6I#mA
```

Does your service prohibit symbols?

```
$ sillypass --symbols ""
dcCnIqcbohv5rBPbpiPiS3WE
```

Does your service limit you to uppercase and lowercase ASCII letters only?

```
$ sillypass --length 24 --min-letters 24
JwzDWLZDeyHXFsSPmsZqaDiI
```

### Limitations and workarounds

`sillypass` can't accommodate every potential restriction a service may place on your password. In these cases, it's usually pretty easy to work around this by generating a less restrictive password with `sillypass` and then editing it by hand afterwards.

Does your service limit your password to no more than 2 symbols? `sillypass` handles minimum-count requirements well, but it doesn't support maximum-count requirements. The easiest approach here is probably to generate a longer password than you want and then delete any extra symbols:

```
$ sillypass --length 32
bHc@SrFX0F6kEO(rj!ByHy3v0E^J&X}]
```

And then edit it down to `bHc@SrFX0F6kEO(rjByHy3v0`. A more automated approach would be to make sure the minimum counts of each character group add up to the total length, implying that the minimum counts will be the exact counts:

```
$ sillypass --length 24 --min-letters 18 --min-digits 4 --min-symbols 2
vrAIYy&YTHQL9x0[Dg7hSQi0
```

Does your service require your password to start with a letter? `sillypass` doesn't support positional restrictions like this. The easiest approach is probably to keep generating passwords until you get one that starts with a letter. A more direct approach (but longer to type) would be to combine two calls to `sillypass`:

```
$ echo $(sillypass --length 1 --min-letters 1)$(sillypass --length 23)
Y:vI$F_MT]K5wfyT/WNymdLX
```

## Usage as an imported package

You can use `sillypass` in your Python project by importing it as a package. It is a very simple package, exposing two functions:

- `create_password()`
- `create_counts()`

and a set of pre-defined character groups:

- `LETTERS`
- `UPPERCASE`
- `LOWERCASE`
- `DIGITS`
- `SYMBOLS`

Use `create_password` to generate a random string containing a specified number of characters from various character groups. For example:

```
import secrets
from sillypass import create_password

rng = secrets.SystemRandom()

counts = {
    "abc": 5,
    "123456": 1,
    "@": 1,
}

password = create_password(counts=counts, rng=rng)
```

would create a password with 7 characters, 5 of which are chosen from "a", "b", or "c" (repeating the same character multiple times is allowed); 1 of which is chosen from "1", "2", "3", "4", "5", or "6"; and 1 of which would be "@".

Use `create_counts` to create a dictionary suitable for passing to `create_password`. It accepts parameters that mimic the command-line script. For example:

```
from sillypass import create_counts

counts = create_counts(
    length=24,
    min_upper=1,
    min_lower=1,
    min_digits=1,
)
```

would create the following dictionary:

```
{
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ": 1,
    "abcdefghijklmnopqrstuvwxyz": 1,
    "0123456789" 1,
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~": 21,
}
```

That last entry is the "remainder" group, formed by the union of letters, digits, and symbols by default. (You can provide your own remainder group with the `remainder` argument.) Its count is computed to pad the length to the total desired length.
