Статьи

Секретная ротация для токенов JWT

Когда вы используете JSON Web Token ( JWT ) или любую другую технологию токена, которая требует подписи или шифрования информации полезной нагрузки, важно установить дату окончания срока действия токена, поэтому, если токен истекает, вы можете предположить, что это может быть рассмотренным как нарушение безопасности, и вы отказываетесь от любого обмена данными с использованием этого токена, или вы решили включить токен, обновив его с новой датой истечения срока действия.

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

Есть несколько стратегий для реализации этого, но в этом посте я собираюсь объяснить, как я реализовал секретную ротацию в одном проекте, который я разработал несколько лет назад, чтобы подписать
Токены JWT с алгоритмом HMAC.

Я собираюсь показать, как создать токен JWT в Java.

01
02
03
04
05
06
07
08
09
10
11
12
try {
 
    Algorithm algorithm = Algorithm.HMAC256("secret");
    String token = JWT.create()
        .withIssuer("auth0")
        .sign(algorithm);
 
} catch (UnsupportedEncodingException exception){
    //UTF-8 encoding not supported
} catch (JWTCreationException exception){
    //Invalid Signing configuration / Couldn't convert Claims.
}

Обратите внимание, что здесь нужно создать объект алгоритма, задающий алгоритм HMAC, и установить секрет, который используется для подписи и проверки экземпляра.

Поэтому нам нужно вращать этот экземпляр алгоритма каждые X минут, чтобы вероятность взлома секрета и того, что взломанный секрет все еще действителен, становится очень низкой.

Так как вращать секреты? Ну, с действительно простым алгоритмом, который может понять каждый (даже если вы не специалист по криптографии). Просто используя время.

Таким образом, чтобы сгенерировать секрет, вам нужна строка, в предыдущем примере была секретная строка, конечно, это не так безопасно, поэтому идея состоит в том, чтобы составить эту секретную строку с помощью корня (то, что мы назвали частью большого взрыва) + сдвинутый неполный рабочий день. В итоге секрет заключается в том, что <bigbang> + <timeInMilliseconds>

Часть Bing Bang не имеет загадки, это просто статическая часть, например, my_super_secret .

Интересная часть — это временная часть. Предположим, вы хотите обновлять секрет каждую секунду. Вам нужно только сделать это:

1
2
3
4
5
6
7
8
9
long t = System.currentTimeMillis();
 
System.out.println(t);
System.out.println((t/1000)*1000);
 
TimeUnit.MILLISECONDS.sleep(50);
 
t = System.currentTimeMillis();
System.out.println((t/1000)*1000);

Я просто помещаю 0s в миллисекунды, поэтому, если я запускаю это, я получаю нечто вроде:

1
2
3
<i>1515091335543</i>
<i>1515091335500</i>
<i>1515091335500</i>

Обратите внимание, что хотя между второй и третьей печатью прошло 50 миллисекунд, временная часть точно такая же. И это будет то же самое в течение той же секунды.

Конечно, это крайний пример, когда секрет меняется каждую секунду, но идея в том, что вы удаляете часть времени, которую хотите игнорировать, и заполняете ее нулями. По этой причине сначала вы делите время, а затем умножаете на одно и то же число.

Например, предположим, что вы хотите вращать секрет каждые 10 минут, вам просто нужно разделить и умножить на 600000.

Есть два проблемы с этим подходом, которые могут быть исправлены, хотя одна из них не является большой проблемой.

Во-первых, поскольку вы усекаете время, если вы хотите менять секрет каждую минуту, и, например, первое вычисление происходит в середине минуты, то только для этого начального случая вращение произойдет через 30 секунд и не 1 минута Не большая проблема, и в нашем проекте мы ничего не сделали, чтобы это исправить.

Второй — это то, что происходит с токенами, которые были подписаны незадолго до секретной ротации, они все еще действительны, и вы должны иметь возможность проверить их тоже, не с новым секретом, а с предыдущим.

Чтобы исправить это, мы создали действительное окно, в котором также сохранялся предыдущий действительный секрет. Поэтому, когда система получает токен, она сверяется с текущим секретом, если он проходит, мы можем выполнять любые другие проверки и работать с ним, если нет, то токен проверяется по предыдущему секрету. Если он проходит, токен воссоздается и подписывается новым секретом, а если нет, то, очевидно, этот токен недействителен и должен быть отклонен.

Чтобы создать объект алгоритма для JWT, вам нужно всего лишь сделать что-то вроде:

1
2
3
4
5
6
7
long currentTime = System.currentTimeMillis();
 
try {
  return Algorithm.HMAC256("my_big_bang" + (currentTime/60000)*60000);
} catch (UnsupportedEncodingException e) {
  throw new IllegalArgumentException(e);
}

Что мне действительно нравится в этом решении:

  • Это чисто, нет необходимости в дополнительных элементах в вашей системе.
  • Нет необходимости в запущенных потоках, которые запускаются асинхронно для обновления секрета.
  • Он действительно эффективен, вам не нужен доступ к внешней системе.
  • Тестирование сервиса действительно просто.
  • Процесс проверки ответственен за вращение секрета.
  • Это действительно легко масштабировать, на самом деле вам ничего не нужно делать, вы можете добавлять все больше и больше экземпляров одного и того же сервиса, и все они будут вращать секрет одновременно, и все они будут использовать тот же секрет, поэтому процесс ротации действительно не имеет состояния, вы можете увеличивать или уменьшать свои экземпляры, и все экземпляры будут по-прежнему иметь возможность проверять токены, подписанные другими экземплярами.

Но, конечно, есть некоторые недостатки:

  • Вам все еще нужно поделиться частью секрета (частью большого взрыва) для каждой из служб безопасным способом. Возможно, используя секреты Kubernetes, Vault от Hashicorp или если вы не используете микросервисы, вы можете просто скопировать файл в конкретное место, а когда служба запущена и работать, прочитать часть большого взрыва, а затем просто удалить ее.
  • Если ваши физические серверы находятся в разных часовых поясах, использование этого подхода может быть более проблематичным. Также вам необходимо, чтобы серверы были более или менее синхронизированы. Поскольку вы храните предыдущий токен и текущий токен, нет необходимости синхронизировать их в одну и ту же секунду, и задержка в несколько секунд все еще возможна без каких-либо проблем.

Итак, мы увидели очень простой способ вращения секретов, чтобы вы могли хранить свои токены в безопасности. Конечно, есть и другие способы сделать то же самое. В этом посте я только что объяснил, как я это сделал в монолитном приложении, которое мы разработали три года назад, и оно работало очень хорошо.

Мы продолжаем учиться,

Alex

Опубликовано на Java Code Geeks с разрешения Алекса Сото, партнера нашей программы JCG . Смотреть оригинальную статью здесь: Секретная ротация для токенов JWT

Мнения, высказанные участниками Java Code Geeks, являются их собственными.