当您使用JSON Web令牌 ( JWT )或需要对有效载荷信息进行签名或加密的任何其他令牌技术时,设置令牌的到期日期很重要,因此,如果令牌到期,则可以假定这可能被视为安全漏洞,您拒绝使用此令牌进行任何通信,或者您决定通过使用新的到期日期更新令牌来启用该令牌。
但是使用某种类型的秘密轮换算法也很重要,因此用于签名或加密令牌的秘密会定期更新,因此,如果该秘密受到威胁,则此密钥泄漏的令牌会更少。 同样,您也可以减少机密被破坏的可能性。
有几种策略可以实现这一目标,但是在这篇文章中,我将解释我如何在几年前开发的一个项目中实施秘密轮换
具有HMAC算法的JWT令牌。
我将展示如何在Java中创建JWT令牌。
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 。
有趣的部分是时间部分。 假设您想每秒更新一次秘密。 您只需要这样做:
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);
我只是将0设置为毫秒部分,所以如果运行此命令,我将得到类似以下内容:
1515091335543
1515091335500
1515091335500
请注意,尽管在第二和第三次打印之间已经过了50毫秒,但时间部分是完全相同的。 在同一秒内将是相同的。
当然,这是一个极端的示例,其中秘密每秒钟更改一次,但其想法是您删除要忽略的部分时间,并用0填充。 因此,首先,您要除以时间,然后再乘以相同的数字。
例如,假设您想每10分钟旋转一次秘密,您只需要除以600000就可以了。
这种方法有两个问题可以解决,尽管其中一个并不是很大的问题。
第一个是因为如果您想每分钟更改一次机密,则您要截断时间,例如,第一次计算是在一分钟的中间进行的,因此对于这种初始情况,轮换将在30秒后发生,并且不是1分钟。 这不是一个大问题,在我们的项目中,我们没有做任何修复。
第二个是在秘密轮换之前签署的令牌所发生的情况,它们仍然有效,您还需要能够验证它们,而不是使用新的秘密,而是使用先前的一个。
为了解决这个问题,我们要做的是创建一个有效窗口,其中还保留了先前的有效机密。 因此,当系统收到令牌时,将使用当前机密验证该令牌,如果该令牌通过,则我们可以进行其他任何检查并使用它,如果未通过,则令牌将由先前的机密验证。 如果通过,则重新创建令牌并使用新的秘密对其进行签名;如果未通过,则显然此令牌无效,必须拒绝。
要为JWT创建算法对象,您只需执行以下操作:
long currentTime = System.currentTimeMillis();try {return Algorithm.HMAC256("my_big_bang" + (currentTime/60000)*60000);
} catch (UnsupportedEncodingException e) {throw new IllegalArgumentException(e);
}
我真正喜欢这种解决方案的地方是:
- 它很干净,不需要系统上的其他元素。
- 不需要异步运行的触发线程来更新密码。
- 它确实性能出色,您无需访问外部系统。
- 测试服务真的很容易。
- 验证过程负责旋转机密。
- 这确实很容易扩展,实际上,您无需执行任何操作,可以添加越来越多的同一服务实例,并且所有实例都将同时旋转秘密,并且所有实例都将使用相同的秘密,因此轮换过程实际上是无状态的,您可以按比例放大或缩小实例,所有实例将继续能够验证其他实例签名的令牌。
但是当然有一些缺点:
- 您仍然需要以安全的方式向每个服务共享机密的一部分(大爆炸部分)。 也许使用Kubernetes机密,Hashicorp的Vault,或者如果您不使用微服务,则可以将文件复制到具体位置,并在服务启动和运行时阅读大爆炸部分,然后将其删除。
- 如果您的物理服务器位于不同的时区,则使用此方法可能会遇到更多问题。 另外,您需要使服务器或多或少同步。 由于您要存储以前的令牌和当前令牌,因此不必在同一秒内同步它们,并且几秒钟的延迟仍然可能没有问题。
因此,我们已经看到了一种非常简单的秘密轮换方式,可以使令牌更安全。 当然,还有其他方法可以做到这一点。 在本文中,我只是解释了我是如何在三年前开发的整体应用程序中做到这一点的,它确实运行良好。
我们不断学习,
亚历克斯
翻译自: https://www.javacodegeeks.com/2018/01/secret-rotation-jwt-tokens.html