默认django密码格式的spring密码编码器(pbkdf2\u sha256)

7kqas0il  于 2021-07-13  发布在  Java
关注(0)|答案(1)|浏览(623)

我正在使用springsecurity开发一个spring引导应用程序,它连接到最初由django生成的数据库,其中所有用户密码都存储在django的数据库中 pbkdf2_sha256 格式。

<algorithm>$<iterations>$<salt>$<hash>

看看Spring的保安 Pbkdf2PasswordEncoder ,我基本上能够找到它,并创建一个编码器,能够编码和匹配django的格式。
在成功地对它进行了单元测试之后,我在django生成的示例值上进行了尝试。。。结果失败了。
为什么这个编码器与django生成的值不匹配?django在引擎盖下表演~magic~?我还尝试了多种java的替代品 SecurityFactory 实施。django源代码中的这一行也引起了我的注意,但编写等效的kotlin并没有解决这个问题。

class DjangoPbkdf2PasswordEncoder : PasswordEncoder {

    companion object {
        private const val PREFIX = "pbkdf2_sha256"
        private const val SEPARATOR = "\$"
        private const val ITERATIONS = 180000
        private const val HASH_WIDTH = 256
        private const val ALGORITHM = "PBKDF2WithHmacSHA256"
    }

    private val saltGenerator: BytesKeyGenerator = KeyGenerators.secureRandom()

    private fun base64Decode(string: String): ByteArray {
        return Base64.getDecoder().decode(string)
    }

    private fun base64Encode(bytes: ByteArray): String {
        return Base64.getEncoder().encodeToString(bytes)
    }

    override fun encode(rawPassword: CharSequence): String {
        val salt = saltGenerator.generateKey()
        val hash = encodeWithSalt(rawPassword, salt)

        val encodedHash = base64Encode(hash)
        val encodedSalt = base64Encode(salt)

        return listOf(PREFIX, ITERATIONS, encodedSalt, encodedHash).joinToString(SEPARATOR)
    }

    private fun encodeWithSalt(rawPassword: CharSequence, salt: ByteArray): ByteArray {
        return encodeWithSaltAndIterations(rawPassword, salt, ITERATIONS)
    }

    private fun encodeWithSaltAndIterations(rawPassword: CharSequence, salt: ByteArray, iterations: Int): ByteArray {
        val keySpec = PBEKeySpec(
            rawPassword.toString().toCharArray(),
            salt,
            iterations,
            HASH_WIDTH
        )

        return try {
            SecretKeyFactory.getInstance(ALGORITHM)
                .generateSecret(keySpec)
                .encoded
        } catch (e: GeneralSecurityException) {
            throw IllegalStateException("Could not create hash", e)
        }
    }

    override fun matches(rawPassword: CharSequence, partsEncodedPassword: String): Boolean {
        if (!partsEncodedPassword.startsWith(PREFIX)) {
            throw IllegalArgumentException("Encoded password does not start with: $PREFIX")
        }

        val parts = partsEncodedPassword.split(SEPARATOR)
        if (parts.size != 4) {
            throw IllegalArgumentException("The encoded password format does not have 4 parts")
        }

        val iterations = parts[1].toInt()
        val salt = base64Decode(parts[2])
        val hash = base64Decode(parts[3])

        return MessageDigest.isEqual(
            hash,
            encodeWithSaltAndIterations(rawPassword, salt, iterations)
        )
    }
}
jgzswidk

jgzswidk1#

我在进一步检查django源代码时找到了罪犯。它使用这个函数来生成一个ascii字符串作为salt。这意味着对salt使用base64编码,就像我在我的方法中所做的那样,是不正确的。要解决此问题:

// In encode()
val salt = saltGenerator.generateKey()
// ...
val encodedSalt = base64Encode(salt)

// In matches()
val salt = base64Decode(parts[2])

应替换为

val salt = generateSaltString()
val hash = encodeWithSalt(rawPassword, salt.toByteArray(Charsets.US_ASCII))

val salt = parts[2].toByteArray(Charsets.US_ASCII)

相关问题