Последнее время пребываю в легком шоке от происходящего. И крупные компании, и Open Source сообщество показали чего стоят либеральные ценности. Риторика и пропаганда с обеих сторон просто поражает своим цинизмом и “человеколюбием”...

Все свои данные срочным порядком перенёс из внешних по отношении к РФ сервисов, сделал локальные бэкапы. Если перенос файлов, документов, почты, репозиториев сложностей не вызывает, то с паролями не так всё просто. Менеджеры паролей удобны, но уровень доверия к сторонним решениям всегда был невысок. При этом “набросать” своё решение вполне реально. Можно считать эту статью краткой инструкцией, поэтому: меньше слов, больше кода.

Crypto API есть и на фронте и на бэке, его подкрепляет множество стандартов. Однако, быстрее всего решить эту задачу через Bash. Должно получиться примерно 100 строк вместе с хэлпом. Суть решения от этого не поменяются, а, в моём случае, на этом можно и остановиться.

Как хранить секреты

Выбираем алгоритм шифрования на свой вкус. В данном случае вполне подойдёт AES-256 в режиме CBC. Про режимы шифрования есть статья на wiki. Получаем следующую команду для шифрования секретов:

$ echo -n "<секрет>" | openssl aes-256-cbc -nosalt -pbkdf2 -base64

Опция -n в данном случае позволяет избавиться от символа перевода строки в конце, в противном случае, перевод строки тоже будет закодирован openssl.

PBKDF2 - стандарт формирования ключа на основе пароля - необходимо указать во избежание назойливого warning.

В момент вызова openssl попросит ввести пароль, который в данном случае является мастер-паролем. На выходе получим зашифрованный секрет в формате Base64, который можно “положить” в любое удобное место. Например, в пользовательскую директорию:

$ echo -n "<секрет>" | openssl aes-256-cbc -nosalt -pbkdf2 -base64 \
                       > ~/.imn/"<имя секрета>".pub

Копию я закинул на Яндекс.Диск ...

Соответственно, чтобы восстановить зашифрованный секрет достаточно произвести обратную операцию:

$ openssl aes-256-cbc -pbkdf2 -base64 -d < ~/.imn/"<имя секрета>".pub

Опция -d указывает на дешифрование, остальные опции openssl неизменны по понятным причинам. При вызове потребуется ввести мастер-пароль.

Вот так просто и без затей... Осталось несколько нюансов.

Как генерировать секреты

Брать секреты “из головы” не только неудобно (при наличии мастер-пароля), но и небезопасно. Наша утилита должна уметь генерировать криптостойкие секреты, по аналогии с многочисленными собратьями.

И снова openssl помогает решить проблему:

$ openssl rand -base64 "<длина секрета>"

Кратная трём длина секрета позволит избежать знаков равенства в конце генерируемой строки, которые служат в кодировке Base64 для “набивки”.

Удобно совместить операции генерации, шифрования и сохранения секрета:

$ local secret=$(openssl rand -base64 "<длина секрета>")
$ echo -n "$secret" | openssl aes-256-cbc -nosalt -pbkdf2 -base64 \
                      > ~/.imn/"<имя секрета>".pub

Копирование “свежего” секрета в буфер обмена варьируется от операционной системы. В Linux - это, скорее всего, xlip:

$ echo "$secret" | xclip

В WSL под Windows - это clip.exe:

$ echo "$secret" | clip.exe

Как импортировать секреты из Chrome

Хранить секреты в менеджере паролей Chrome конечно удобно, но... с учетом новых обстоятельств, лучше не стоит.

Перенос секретов из Chrome, ровно как и из других менеджеров паролей - задача тривиальная. Экспортируем секреты в формате CSV. Получится нечто следующее:

# name,url,usename,password
,<https://foo.com/bar,ivanov,qwerty>
,<https://plugh.com/quux,petrov,asdfg>

Первая строка в CSV формате - это перечисление имён колонок. Отбрасываем строку с метаинформацией (парсим начиная со второй +2):

$ cat chrome-secrets.csv | tail -n+2

И парсим при помощи read строка за строкой:

cat chrome-secrets.csv | tail -n+2 | \
  while IFS=',' read -r name url username secret; do
    echo Имя секрета: "$url@$username"
    echo Секрет: "$secret"
  done

В качестве уникального имени секрета лучше использовать сочетание URL и логин.

Полный листинг

Чтобы зря не тратить ваше время приведу то, что получилось у меня:

#!/usr/bin/env bash

DIR=~/.imn
ALGO=(aes-256-cbc -pbkdf2 -base64)

mkdir -p "$DIR"

store() {
  local name="$1"
  shift
  local secret
  secret=$(rand_secret "$@")

  store_secret "$name" "$secret"
}

rand_secret() {
  if [[ $# -gt 0 ]]; then
    openssl rand "$@"
  else
    openssl rand -base64 30
  fi
}

store_secret() {
  local name="$1"
  local secret="$2"
  shift 2

  echo -n "$secret" | openssl "${ALGO[@]}" "$@" > "$DIR"/"$name".pub

  print_secret "$secret"
}

restore() {
  assert_pub_key "$1"
  local name="$1"

  local secret
  secret=$(openssl "${ALGO[@]}" -d < "$DIR"/"$name".pub)

  print_secret "$secret"
}

assert_pub_key() {
  local name="$1"
  if [[ ! -f "$DIR"/"$name".pub ]]; then
    echo Public key "\"$name\"" not found >&2
    exit 1
  fi
}

print_secret() {
  printf '%s\n' "$1"
}

import_chrome_csv() {
  # name,url,usename,password
  cut -f2-4 -d',' | import_csv "$@"
}

import_csv() {
  grep -v password | while IFS=',' read -r url username secret; do
    local origin="${url#*://}"
    origin="${origin%%/*}"
    local name="$origin@$username"
    echo "$name"
    store_secret "$name" "$secret" "$@" >/dev/null
  done
}

case "$1" in
  --help|-h)
    echo "Generate and store random secret:"
    echo "  imn.sh store|s <name>"
    echo "Restore secret:"
    echo "  imn.sh [restore|r] <name>"
    echo "Import secrets from Chrome's CSV:"
    echo "  imn.sh chrome <path to CSV> -k <master password>"
  ;;
  chrome)
    shift
    csv=$1
    shift
    import_chrome_csv "$@" < "$csv"
    ;;
  store|s|gen|g)
    shift
    store "$@"
    ;;
  restore|r)
    shift
    restore "$@"
    ;;
  *)
    restore "$@"
    ;;
esac

Если Вы такой же параноик (в хорошем смысле) как и я, милости прошу в комментарии, всегда рад пообщаться с единомышленниками.

Комментарии (20)


  1. kovserg
    22.03.2022 16:51
    +1

    Чем не устроил pass


    1. radneck Автор
      22.03.2022 16:55

      Решений масса, но моя паранойя привела меня к DIY ;)

      Люблю всё готовить сам)


    1. mibori
      22.03.2022 17:18
      +1

      хотя автор совершает ту же ошибку, меня, вот, например, в pass не устраивает то, что владелец хостинга знает, какими сервисами ты пользуешься, либо тебе приходится шифровать/облако репозиторий.


      1. radneck Автор
        22.03.2022 17:22

        Тема посильная всем. Поэтому любые ваши идеи вполне можно самостоятельно реализовать, отталкиваясь от данного примитивного решения.


  1. Akon32
    22.03.2022 16:56
    +1

    Почему бы не использовать OpenPGP / GPG?


  1. twelve
    22.03.2022 17:01
    +1

    Какой же вы параноик, если у вас хэши не солёные, хотя есть возможность?


    1. radneck Автор
      22.03.2022 17:03

      Ахах. Алгоритм можно любой выбрать на свой вкус. В том числе и с солью. Перекодировать по крону будете?)


  1. polearnik
    22.03.2022 17:28
    +8

    используйте keepass и не парьте себе и окружающим мозги. если переживаете о том что изза вашего гражданства пароли могут быть слиты то берите релиз до конца февраля. ИНаче создаете опасное и обманчивое впечатление что работа с криптографией это просто и безопасно.


    1. radneck Автор
      22.03.2022 17:32

      Понял. Принял)


  1. imbasoft
    22.03.2022 17:44
    +1

    Соль в данном случае не ясно для чего может пригодиться. "

    Соль в данном случае требуется равно для того же, для чего и всегда, чтобы одинаковые зашифрованные отличались между собой (при условии что используется одинаковый ключ шифрования).

    Например, в сайта site.com у вас пароль "1" и для шифрования вы используете ключ, формируемый из пароля 1.

    echo -n "1" | openssl aes-256-cbc -nosalt -pbkdf2 -base64
    enter aes-256-cbc encryption password:
    Verifying - enter aes-256-cbc encryption password:
    fu1Qc8Y+YiB4bZ0UZhWhLA==

    Если для сайта site.ru вы используете тот же пароль, то зашифрованные данные fu1Qc8Y+YiB4bZ0UZhWhLA== очевидно будут совпадать.

    Тут разумно сказать, что из fu1Qc8Y+YiB4bZ0UZhWhLA== получить искомый пароль 1 довольно сложно. НО! Если злоумышленник похачит site.com, то он автоматически получит доступ и на site.ru, что не есть здорово.

    Безопасность складывается из мелочей, чем больше мелочей отбрасывается, тем больше безопасность превращается в иллюзию безопасности.


    1. radneck Автор
      22.03.2022 17:48

      Это понятно. Согласен. С учетом того, что сам секрет генерится рандомно, этот подход особо ничего не даёт.


  1. Revertis
    22.03.2022 17:47

    Зачем что-то хранить, если всегда можно перегенерировать?


    1. radneck Автор
      22.03.2022 17:49

      Это повод для ещё одного DIY решения)


      1. Revertis
        22.03.2022 17:59
        +1

        Был. Я уже сделал всё правильно :)

        Можете только аппку под iOS написать ;)


        1. radneck Автор
          22.03.2022 18:22
          +1

          Красавчик)


    1. Ritan
      22.03.2022 18:05
      +1

      Затем, что при таком способе компрометация(а точнее последующая смена) одного пароля требует смены мастер-пароля, а следовательно смены паролей во всех сервисах, пароли которых сгенерированы таким образом


      1. Revertis
        22.03.2022 18:13
        +1

        Зачем менять весь мастер-пароль? Если на каком-то сервисе произошла утечка, или просто требуют сменить пароль, к мастеру добавляете 1, и весь новый пароль меняется. За все много лет использования мне нужно было такое 2 раза, и уж любой конкретный единичный случай поддаётся запоминанию.


        1. Ritan
          22.03.2022 19:03

          Добавили к мастер-паролю 1 - изменились все сгенерированный пароли. Т.е. теперь варианты: либо хранить где-то список сервисов, для которых происходили утечки, чтобы добавлять единицу(две единицы итд) либо менять пароли всех сервисов при каждой утечке. А утечки сейчас происходят с завидным постоянством


          1. Revertis
            22.03.2022 19:05

            Я вам пишу: список заменённых паролей состоит из 1-2 объектов. Замечательно помещается в памяти среднего человека.


  1. Kirikekeks
    22.03.2022 23:25

    Мне удобнее монтировать файл как luks диск. И уже в шифрованном пространстве вести человеческие пароли, заметки. И я делаю его неудаляемым через chattr, что и в Ваш скрипт добавить - две строки, а польза большая.