首页 > 其他分享 >Abp vNext Secret

Abp vNext Secret

时间:2023-12-08 09:33:52浏览次数:42  
标签:vNext iterations secret Abp Secret key new password salt

Abp vNext Secret

使用Abp vNext 6.0
abp大概有两个secret,AbpUsersOpenIddictApplications

AbpUsers

abp的用户管理IdentityUserManager其实是直接套的aspnetcore的UserManager,继承完就没怎么改了,所以看源码要看aspnetcore的源码

我大概调试到最底下是NetCorePbkdf2Provider这个地方的HMACSHA256

internal sealed class NetCorePbkdf2Provider : IPbkdf2Provider
{
    public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
    {
        Debug.Assert(password != null);
        Debug.Assert(salt != null);
        Debug.Assert(iterationCount > 0);
        Debug.Assert(numBytesRequested > 0);

        HashAlgorithmName algorithmName;
        switch (prf)
        {
            case KeyDerivationPrf.HMACSHA1:
                algorithmName = HashAlgorithmName.SHA1;
                break;
            case KeyDerivationPrf.HMACSHA256:
                algorithmName = HashAlgorithmName.SHA256;
                break;
            case KeyDerivationPrf.HMACSHA512:
                algorithmName = HashAlgorithmName.SHA512;
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(prf));
        }

        return Rfc2898DeriveBytes.Pbkdf2(password, salt, iterationCount, algorithmName, numBytesRequested);
    }
}

Rfc2898DeriveBytes.Pbkdf2的源码在runtime里

/// <summary>
/// Creates a PBKDF2 derived key from a password.
/// </summary>
/// <param name="password">The password used to derive the key.</param>
/// <param name="salt">The key salt used to derive the key.</param>
/// <param name="iterations">The number of iterations for the operation.</param>
/// <param name="hashAlgorithm">The hash algorithm to use to derive the key.</param>
/// <param name="outputLength">The size of key to derive.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="password" /> or <paramref name="salt" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
///   <para><paramref name="outputLength" /> is not zero or a positive value.</para>
///   <para>-or-</para>
///   <para><paramref name="iterations" /> is not a positive value.</para>
/// </exception>
/// <exception cref="ArgumentException">
///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" />
///   that is empty or <see langword="null" />.
/// </exception>
/// <exception cref="CryptographicException">
///   <paramref name="hashAlgorithm" /> is an unsupported hash algorithm. Supported algorithms
///   are <see cref="HashAlgorithmName.SHA1" />, <see cref="HashAlgorithmName.SHA256" />,
///   <see cref="HashAlgorithmName.SHA384" />, and <see cref="HashAlgorithmName.SHA512" />.
/// </exception>
/// <exception cref="EncoderFallbackException">
/// <paramref name="password" /> contains text that cannot be converted to UTF-8.
/// </exception>
/// <remarks>
/// The <paramref name="password" /> will be converted to bytes using the UTF-8 encoding. For
/// other encodings, convert the password string to bytes using the appropriate <see cref="System.Text.Encoding" />
/// and use <see cref="Pbkdf2(byte[], byte[], int, HashAlgorithmName, int)" />.
/// </remarks>
public static byte[] Pbkdf2(
    string password,
    byte[] salt,
    int iterations,
    HashAlgorithmName hashAlgorithm,
    int outputLength)
{
    ArgumentNullException.ThrowIfNull(password);
    ArgumentNullException.ThrowIfNull(salt);

    return Pbkdf2(password.AsSpan(), new ReadOnlySpan<byte>(salt), iterations, hashAlgorithm, outputLength);
}

/// <summary>
/// Creates a PBKDF2 derived key from password bytes.
/// </summary>
/// <param name="password">The password used to derive the key.</param>
/// <param name="salt">The key salt used to derive the key.</param>
/// <param name="iterations">The number of iterations for the operation.</param>
/// <param name="hashAlgorithm">The hash algorithm to use to derive the key.</param>
/// <param name="outputLength">The size of key to derive.</param>
/// <exception cref="ArgumentNullException">
/// <paramref name="password" /> or <paramref name="salt" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
///   <para><paramref name="outputLength" /> is not zero or a positive value.</para>
///   <para>-or-</para>
///   <para><paramref name="iterations" /> is not a positive value.</para>
/// </exception>
/// <exception cref="ArgumentException">
///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" />
///   that is empty or <see langword="null" />.
/// </exception>
/// <exception cref="CryptographicException">
///   <paramref name="hashAlgorithm" /> is an unsupported hash algorithm. Supported algorithms
///   are <see cref="HashAlgorithmName.SHA1" />, <see cref="HashAlgorithmName.SHA256" />,
///   <see cref="HashAlgorithmName.SHA384" />, and <see cref="HashAlgorithmName.SHA512" />.
/// </exception>
public static byte[] Pbkdf2(
    byte[] password,
    byte[] salt,
    int iterations,
    HashAlgorithmName hashAlgorithm,
    int outputLength)
{
    ArgumentNullException.ThrowIfNull(password);
    ArgumentNullException.ThrowIfNull(salt);

    return Pbkdf2(new ReadOnlySpan<byte>(password), new ReadOnlySpan<byte>(salt), iterations, hashAlgorithm, outputLength);
}

/// <summary>
/// Creates a PBKDF2 derived key from password bytes.
/// </summary>
/// <param name="password">The password used to derive the key.</param>
/// <param name="salt">The key salt used to derive the key.</param>
/// <param name="iterations">The number of iterations for the operation.</param>
/// <param name="hashAlgorithm">The hash algorithm to use to derive the key.</param>
/// <param name="outputLength">The size of key to derive.</param>
/// <exception cref="ArgumentOutOfRangeException">
///   <para><paramref name="outputLength" /> is not zero or a positive value.</para>
///   <para>-or-</para>
///   <para><paramref name="iterations" /> is not a positive value.</para>
/// </exception>
/// <exception cref="ArgumentException">
///   <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" />
///   that is empty or <see langword="null" />.
/// </exception>
/// <exception cref="CryptographicException">
///   <paramref name="hashAlgorithm" /> is an unsupported hash algorithm. Supported algorithms
///   are <see cref="HashAlgorithmName.SHA1" />, <see cref="HashAlgorithmName.SHA256" />,
///   <see cref="HashAlgorithmName.SHA384" />, and <see cref="HashAlgorithmName.SHA512" />.
/// </exception>
public static byte[] Pbkdf2(
    ReadOnlySpan<byte> password,
    ReadOnlySpan<byte> salt,
    int iterations,
    HashAlgorithmName hashAlgorithm,
    int outputLength)
{
    ArgumentOutOfRangeException.ThrowIfNegativeOrZero(iterations);
    ArgumentOutOfRangeException.ThrowIfNegative(outputLength);

    ValidateHashAlgorithm(hashAlgorithm);

    byte[] result = new byte[outputLength];
    Pbkdf2Core(password, salt, result, iterations, hashAlgorithm);
    return result;
}

private static void Pbkdf2Core(
    ReadOnlySpan<char> password,
    ReadOnlySpan<byte> salt,
    Span<byte> destination,
    int iterations,
    HashAlgorithmName hashAlgorithm)
{
    Debug.Assert(hashAlgorithm.Name is not null);
    Debug.Assert(iterations > 0);

    if (destination.IsEmpty)
    {
        return;
    }

    const int MaxPasswordStackSize = 256;

    byte[]? rentedPasswordBuffer = null;
    int maxEncodedSize = s_throwingUtf8Encoding.GetMaxByteCount(password.Length);

    Span<byte> passwordBuffer = maxEncodedSize > MaxPasswordStackSize ?
        (rentedPasswordBuffer = CryptoPool.Rent(maxEncodedSize)) :
        stackalloc byte[MaxPasswordStackSize];
    int passwordBytesWritten = s_throwingUtf8Encoding.GetBytes(password, passwordBuffer);
    Span<byte> passwordBytes = passwordBuffer.Slice(0, passwordBytesWritten);

    try
    {
        Pbkdf2Implementation.Fill(passwordBytes, salt, iterations, hashAlgorithm, destination);
    }
    finally
    {
        CryptographicOperations.ZeroMemory(passwordBytes);
    }

    if (rentedPasswordBuffer is not null)
    {
        CryptoPool.Return(rentedPasswordBuffer, clearSize: 0); // manually cleared above.
    }
}

上面一串下来就是Pbkdf2Implementation,这个在源码里面是根据操作系统来的,这个是windows的


public static unsafe void Fill(
    ReadOnlySpan<byte> password,
    ReadOnlySpan<byte> salt,
    int iterations,
    HashAlgorithmName hashAlgorithmName,
    Span<byte> destination)
{
    Debug.Assert(!destination.IsEmpty);
    Debug.Assert(iterations >= 0);
    Debug.Assert(hashAlgorithmName.Name is not null);

    if (s_useKeyDerivation)
    {
        FillKeyDerivation(password, salt, iterations, hashAlgorithmName.Name, destination);
    }
    else
    {
        FillDeriveKeyPBKDF2(password, salt, iterations, hashAlgorithmName.Name, destination);
    }
}

private static unsafe void FillKeyDerivation(
    ReadOnlySpan<byte> password,
    ReadOnlySpan<byte> salt,
    int iterations,
    string hashAlgorithmName,
    Span<byte> destination)
{
    SafeBCryptKeyHandle keyHandle;
    int hashBlockSizeBytes = GetHashBlockSize(hashAlgorithmName);

    // stackalloc 0 to let compiler know this cannot escape.
    scoped Span<byte> clearSpan;
    scoped ReadOnlySpan<byte> symmetricKeyMaterial;
    int symmetricKeyMaterialLength;

    if (password.IsEmpty)
    {
        // CNG won't accept a null pointer for the password.
        symmetricKeyMaterial = stackalloc byte[1];
        symmetricKeyMaterialLength = 0;
        clearSpan = default;
    }
    else if (password.Length <= hashBlockSizeBytes)
    {
        // Password is small enough to use as-is.
        symmetricKeyMaterial = password;
        symmetricKeyMaterialLength = password.Length;
        clearSpan = default;
    }
    else
    {
        // RFC 2104: "The key for HMAC can be of any length (keys longer than B bytes are
        //     first hashed using H).
        //     We denote by B the byte-length of such
        //     blocks (B=64 for all the above mentioned examples of hash functions)
        //
        // Windows' PBKDF2 will do this up to a point. To ensure we accept arbitrary inputs for
        // PBKDF2, we do the hashing ourselves.
        Span<byte> hashBuffer = stackalloc byte[512 / 8]; // 64 bytes is SHA512, the largest digest handled.
        int hashBufferSize;

        switch (hashAlgorithmName)
        {
            case HashAlgorithmNames.SHA1:
            case HashAlgorithmNames.SHA256:
            case HashAlgorithmNames.SHA384:
            case HashAlgorithmNames.SHA512:
                hashBufferSize = HashProviderDispenser.OneShotHashProvider.HashData(hashAlgorithmName, password, hashBuffer);
                break;
            case HashAlgorithmNames.SHA3_256:
            case HashAlgorithmNames.SHA3_384:
            case HashAlgorithmNames.SHA3_512:
                if (!HashProviderDispenser.HashSupported(hashAlgorithmName))
                {
                    throw new PlatformNotSupportedException();
                }

                hashBufferSize = HashProviderDispenser.OneShotHashProvider.HashData(hashAlgorithmName, password, hashBuffer);
                break;
            default:
                Debug.Fail($"Unexpected hash algorithm '{hashAlgorithmName}'");
                throw new CryptographicException();
        }

        clearSpan = hashBuffer.Slice(0, hashBufferSize);
        symmetricKeyMaterial = clearSpan;
        symmetricKeyMaterialLength = hashBufferSize;
    }

    Debug.Assert(symmetricKeyMaterial.Length > 0);

    NTSTATUS generateKeyStatus;

    if (Interop.BCrypt.PseudoHandlesSupported)
    {
        fixed (byte* pSymmetricKeyMaterial = symmetricKeyMaterial)
        {
            generateKeyStatus = Interop.BCrypt.BCryptGenerateSymmetricKey(
                (nuint)BCryptAlgPseudoHandle.BCRYPT_PBKDF2_ALG_HANDLE,
                out keyHandle,
                pbKeyObject: IntPtr.Zero,
                cbKeyObject: 0,
                pSymmetricKeyMaterial,
                symmetricKeyMaterialLength,
                dwFlags: 0);
        }
    }
    else
    {
        if (s_pbkdf2AlgorithmHandle is null)
        {
            NTSTATUS openStatus = Interop.BCrypt.BCryptOpenAlgorithmProvider(
                out SafeBCryptAlgorithmHandle pbkdf2AlgorithmHandle,
                Internal.NativeCrypto.BCryptNative.AlgorithmName.Pbkdf2,
                null,
                BCryptOpenAlgorithmProviderFlags.None);

            if (openStatus != NTSTATUS.STATUS_SUCCESS)
            {
                pbkdf2AlgorithmHandle.Dispose();
                CryptographicOperations.ZeroMemory(clearSpan);
                throw Interop.BCrypt.CreateCryptographicException(openStatus);
            }

            // This might race, and that's okay. Worst case the algorithm is opened
            // more than once, and the ones that lost will get cleaned up during collection.
            Interlocked.CompareExchange(ref s_pbkdf2AlgorithmHandle, pbkdf2AlgorithmHandle, null);
        }

        fixed (byte* pSymmetricKeyMaterial = symmetricKeyMaterial)
        {
            generateKeyStatus = Interop.BCrypt.BCryptGenerateSymmetricKey(
                s_pbkdf2AlgorithmHandle,
                out keyHandle,
                pbKeyObject: IntPtr.Zero,
                cbKeyObject: 0,
                pSymmetricKeyMaterial,
                symmetricKeyMaterialLength,
                dwFlags: 0);
        }
    }

    CryptographicOperations.ZeroMemory(clearSpan);

    if (generateKeyStatus != NTSTATUS.STATUS_SUCCESS)
    {
        keyHandle.Dispose();
        throw Interop.BCrypt.CreateCryptographicException(generateKeyStatus);
    }

    Debug.Assert(!keyHandle.IsInvalid);

    ulong kdfIterations = (ulong)iterations; // Previously asserted to be positive.

    using (keyHandle)
    fixed (char* pHashAlgorithmName = hashAlgorithmName)
    fixed (byte* pSalt = salt)
    fixed (byte* pDestination = destination)
    {
        Span<BCryptBuffer> buffers = stackalloc BCryptBuffer[3];
        buffers[0].BufferType = CngBufferDescriptors.KDF_ITERATION_COUNT;
        buffers[0].pvBuffer = (IntPtr)(&kdfIterations);
        buffers[0].cbBuffer = sizeof(ulong);

        buffers[1].BufferType = CngBufferDescriptors.KDF_SALT;
        buffers[1].pvBuffer = (IntPtr)pSalt;
        buffers[1].cbBuffer = salt.Length;

        buffers[2].BufferType = CngBufferDescriptors.KDF_HASH_ALGORITHM;
        buffers[2].pvBuffer = (IntPtr)pHashAlgorithmName;

        // C# spec: "A char* value produced by fixing a string instance always points to a null-terminated string"
        buffers[2].cbBuffer = checked((hashAlgorithmName.Length + 1) * sizeof(char)); // Add null terminator.

        fixed (BCryptBuffer* pBuffers = buffers)
        {
            Interop.BCrypt.BCryptBufferDesc bufferDesc;
            bufferDesc.ulVersion = Interop.BCrypt.BCRYPTBUFFER_VERSION;
            bufferDesc.cBuffers = buffers.Length;
            bufferDesc.pBuffers = (IntPtr)pBuffers;

            NTSTATUS deriveStatus = Interop.BCrypt.BCryptKeyDerivation(
                keyHandle,
                &bufferDesc,
                pDestination,
                destination.Length,
                out uint resultLength,
                dwFlags: 0);

            if (deriveStatus != NTSTATUS.STATUS_SUCCESS)
            {
                throw Interop.BCrypt.CreateCryptographicException(deriveStatus);
            }

            if (destination.Length != resultLength)
            {
                Debug.Fail("PBKDF2 resultLength != destination.Length");
                throw new CryptographicException();
            }
        }
    }
}

总的流程就是前端传密码给后端,后端再通过数据库保存的密文对明文进行hash转换,再用明文的hash比较数据库保存的密文
还真复杂,所以还是用.net或abp自带的函数来操作比较好,不过还是得封装一层,这样才比较符合使用习惯

OpenIddictApplications

根据abp的源码,只能知道是用内置IOpenIddictApplicationManager来操作数据,hash操作估计也在里面,再根据AbpUsers的看来,这个OpenIddictApplicationManager估计就在OpenIddict的源码里

所以接下来的代码是OpenIddict的源码
根据OpenIddict官方所说,这个加密应该是PBKDF2 with HMAC-SHA256,那就跟AbpUsers是一样的了

https://github.com/openiddict/openiddict-core/issues/418

ClientSecret操作大概就在这俩函数里,CreateAsync一看就是创建数据的,ObfuscateClientSecretAsync函数是加密secret的

/// <summary>
/// Creates a new application.
/// Note: the default implementation automatically hashes the client
/// secret before storing it in the database, for security reasons.
/// </summary>
/// <param name="application">The application to create.</param>
/// <param name="secret">The client secret associated with the application, if applicable.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual async ValueTask CreateAsync(TApplication application, string? secret, CancellationToken cancellationToken = default)
{
    if (application is null)
    {
        throw new ArgumentNullException(nameof(application));
    }

    if (!string.IsNullOrEmpty(await Store.GetClientSecretAsync(application, cancellationToken)))
    {
        throw new ArgumentException(SR.GetResourceString(SR.ID0206), nameof(application));
    }

    // If no client type was specified, assume it's a confidential application if a secret was
    // provided or a JSON Web Key Set was attached and contains at least one RSA/ECDSA signing key.
    var type = await Store.GetClientTypeAsync(application, cancellationToken);
    if (string.IsNullOrEmpty(type))
    {
        if (!string.IsNullOrEmpty(secret))
        {
            await Store.SetClientTypeAsync(application, ClientTypes.Confidential, cancellationToken);
        }

        else
        {
            var set = await Store.GetJsonWebKeySetAsync(application, cancellationToken);
            if (set is not null && set.Keys.Any(static key =>
                key.Kty is JsonWebAlgorithmsKeyTypes.EllipticCurve or JsonWebAlgorithmsKeyTypes.RSA &&
                key.Use is JsonWebKeyUseNames.Sig or null))
            {
                await Store.SetClientTypeAsync(application, ClientTypes.Confidential, cancellationToken);
            }

            else
            {
                await Store.SetClientTypeAsync(application, ClientTypes.Public, cancellationToken);
            }
        }
    }

    // If a client secret was provided, obfuscate it.
    if (!string.IsNullOrEmpty(secret))
    {
        secret = await ObfuscateClientSecretAsync(secret, cancellationToken);
        await Store.SetClientSecretAsync(application, secret, cancellationToken);
    }

    var results = await GetValidationResultsAsync(application, cancellationToken);
    if (results.Any(result => result != ValidationResult.Success))
    {
        var builder = new StringBuilder();
        builder.AppendLine(SR.GetResourceString(SR.ID0207));
        builder.AppendLine();

        foreach (var result in results)
        {
            builder.AppendLine(result.ErrorMessage);
        }

        throw new ValidationException(builder.ToString(), results);
    }

    await Store.CreateAsync(application, cancellationToken);

    if (!Options.CurrentValue.DisableEntityCaching)
    {
        await Cache.AddAsync(application, cancellationToken);
    }

    async Task<ImmutableArray<ValidationResult>> GetValidationResultsAsync(
        TApplication application, CancellationToken cancellationToken)
    {
        var builder = ImmutableArray.CreateBuilder<ValidationResult>();

        await foreach (var result in ValidateAsync(application, cancellationToken))
        {
            builder.Add(result);
        }

        return builder.ToImmutable();
    }
}

/// <summary>
/// Obfuscates the specified client secret so it can be safely stored in a database.
/// By default, this method returns a complex hashed representation computed using PBKDF2.
/// </summary>
/// <param name="secret">The client secret.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
/// </returns>
protected virtual ValueTask<string> ObfuscateClientSecretAsync(string secret, CancellationToken cancellationToken = default)
{
    if (string.IsNullOrEmpty(secret))
    {
        throw new ArgumentException(SR.GetResourceString(SR.ID0216), nameof(secret));
    }

    // Note: the PRF, iteration count, salt length and key length currently all match the default values
    // used by CryptoHelper and ASP.NET Core Identity but this may change in the future, if necessary.

    var salt = OpenIddictHelpers.CreateRandomArray(size: 128);
    var hash = HashSecret(secret, salt, HashAlgorithmName.SHA256, iterations: 10_000, length: 256 / 8);

    return new(Convert.ToBase64String(hash));

    // Note: the following logic deliberately uses the same format as CryptoHelper (used in OpenIddict 1.x/2.x),
    // which was itself based on ASP.NET Core Identity's latest hashed password format. This guarantees that
    // secrets hashed using a recent OpenIddict version can still be read by older packages (and vice versa).

    static byte[] HashSecret(string secret, byte[] salt, HashAlgorithmName algorithm, int iterations, int length)
    {
        var key = DeriveKey(secret, salt, algorithm, iterations, length);
        var payload = new byte[13 + salt.Length + key.Length];

        // Write the format marker.
        payload[0] = 0x01;

        // Write the hashing algorithm version.
        BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(1, sizeof(uint)), algorithm switch
        {
            var name when name == HashAlgorithmName.SHA1   => 0,
            var name when name == HashAlgorithmName.SHA256 => 1,
            var name when name == HashAlgorithmName.SHA512 => 2,

            _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0217))
        });

        // Write the iteration count of the algorithm.
        BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(5, sizeof(uint)), (uint) iterations);

        // Write the size of the salt.
        BinaryPrimitives.WriteUInt32BigEndian(payload.AsSpan(9, sizeof(uint)), (uint) salt.Length);

        // Write the salt.
        salt.CopyTo(payload.AsSpan(13));

        // Write the subkey.
        key.CopyTo(payload.AsSpan(13 + salt.Length));

        return payload;
    }
}

至于这个secret,有一个验证函数VerifyHashedSecret,这个在OpenIddictServerHandlers有用到,在OpenIddictApplicationManager里面,似乎是在GrantType之前验证application的

/// <summary>
/// Validates the client_secret associated with an application.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="secret">The secret that should be compared to the client_secret stored in the database.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the client secret was valid.
/// </returns>
public virtual async ValueTask<bool> ValidateClientSecretAsync(
    TApplication application, string secret, CancellationToken cancellationToken = default)
{
    if (application is null)
    {
        throw new ArgumentNullException(nameof(application));
    }
    if (string.IsNullOrEmpty(secret))
    {
        throw new ArgumentException(SR.GetResourceString(SR.ID0216), nameof(secret));
    }

    if (await HasClientTypeAsync(application, ClientTypes.Public, cancellationToken))
    {
        Logger.LogWarning(SR.GetResourceString(SR.ID6159));

        return false;
    }

    var value = await Store.GetClientSecretAsync(application, cancellationToken);
    if (string.IsNullOrEmpty(value))
    {
        Logger.LogInformation(SR.GetResourceString(SR.ID6160), await GetClientIdAsync(application, cancellationToken));

        return false;
    }

    if (!await ValidateClientSecretAsync(secret, value, cancellationToken))
    {
        Logger.LogInformation(SR.GetResourceString(SR.ID6161), await GetClientIdAsync(application, cancellationToken));

        return false;
    }

    return true;
}

/// <summary>
/// Validates the specified value to ensure it corresponds to the client secret.
/// Note: when overriding this method, using a time-constant comparer is strongly recommended.
/// </summary>
/// <param name="secret">The client secret to compare to the value stored in the database.</param>
/// <param name="comparand">The value stored in the database, which is usually a hashed representation of the secret.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation,
/// whose result returns a boolean indicating whether the specified value was valid.
/// </returns>
protected virtual ValueTask<bool> ValidateClientSecretAsync(
    string secret, string comparand, CancellationToken cancellationToken = default)
{
    if (string.IsNullOrEmpty(secret))
    {
        throw new ArgumentException(SR.GetResourceString(SR.ID0216), nameof(secret));
    }

    if (string.IsNullOrEmpty(comparand))
    {
        throw new ArgumentException(SR.GetResourceString(SR.ID0218), nameof(comparand));
    }

    try
    {
        return new(VerifyHashedSecret(comparand, secret));
    }

    catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
    {
        Logger.LogWarning(exception, SR.GetResourceString(SR.ID6163));

        return new(false);
    }

    // Note: the following logic deliberately uses the same format as CryptoHelper (used in OpenIddict 1.x/2.x),
    // which was itself based on ASP.NET Core Identity's latest hashed password format. This guarantees that
    // secrets hashed using a recent OpenIddict version can still be read by older packages (and vice versa).

    static bool VerifyHashedSecret(string hash, string secret)
    {
        var payload = new ReadOnlySpan<byte>(Convert.FromBase64String(hash));
        if (payload.Length is 0)
        {
            return false;
        }

        // Verify the hashing format version.
        if (payload[0] is not 0x01)
        {
            return false;
        }

        // Read the hashing algorithm version.
        var algorithm = (int) BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(1, sizeof(uint))) switch
        {
            0 => HashAlgorithmName.SHA1,
            1 => HashAlgorithmName.SHA256,
            2 => HashAlgorithmName.SHA512,

            _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0217))
        };

        // Read the iteration count of the algorithm.
        var iterations = (int) BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(5, sizeof(uint)));

        // Read the size of the salt and ensure it's more than 128 bits.
        var saltLength = (int) BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(9, sizeof(uint)));
        if (saltLength < 128 / 8)
        {
            return false;
        }

        // Read the salt.
        var salt = payload.Slice(13, saltLength);

        // Ensure the derived key length is more than 128 bits.
        var keyLength = payload.Length - 13 - salt.Length;
        if (keyLength < 128 / 8)
        {
            return false;
        }

        return OpenIddictHelpers.FixedTimeEquals(
            left:  payload.Slice(13 + salt.Length, keyLength),
            right: DeriveKey(secret, salt.ToArray(), algorithm, iterations, keyLength));
    }
}

private static byte[] DeriveKey(string secret, byte[] salt, HashAlgorithmName algorithm, int iterations, int length)
{
#if SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM
    return OpenIddictHelpers.DeriveKey(secret, salt, algorithm, iterations, length);
#else
    var generator = new Pkcs5S2ParametersGenerator(algorithm switch
    {
        var name when name == HashAlgorithmName.SHA1   => new Sha1Digest(),
        var name when name == HashAlgorithmName.SHA256 => new Sha256Digest(),
        var name when name == HashAlgorithmName.SHA512 => new Sha512Digest(),

        _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0217))
    });

    generator.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(secret.ToCharArray()), salt, iterations);

    var key = (KeyParameter) generator.GenerateDerivedMacParameters(length * 8);
    return key.GetKey();
#endif
}

这个流程和AbpUsers的差不多,都是前端传给后端,后端再通过数据库保存的密文对明文进行hash转换,再用明文的hash比较数据库保存的密文

我是没感觉OpenIddictApplications这个ClientSecret有啥用,感觉跟用户名和密码一样,官方文档里说是内部用的,就是说内网操作需要验证咯,那确实

DeriveKey

DeriveKey函数似乎才是具体的算法函数,ObfuscateClientSecretAsyncVerifyHashedSecret都有调用这个

private static byte[] DeriveKey(string secret, byte[] salt, HashAlgorithmName algorithm, int iterations, int length)
{
#if SUPPORTS_KEY_DERIVATION_WITH_SPECIFIED_HASH_ALGORITHM
    return OpenIddictHelpers.DeriveKey(secret, salt, algorithm, iterations, length);
#else
    var generator = new Pkcs5S2ParametersGenerator(algorithm switch
    {
        var name when name == HashAlgorithmName.SHA1   => new Sha1Digest(),
        var name when name == HashAlgorithmName.SHA256 => new Sha256Digest(),
        var name when name == HashAlgorithmName.SHA512 => new Sha512Digest(),

        _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0217))
    });

    generator.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(secret.ToCharArray()), salt, iterations);

    var key = (KeyParameter) generator.GenerateDerivedMacParameters(length * 8);
    return key.GetKey();
#endif
}

Abp vNext Secret结束

标签:vNext,iterations,secret,Abp,Secret,key,new,password,salt
From: https://www.cnblogs.com/zzy-tongzhi-cnblog/p/17880083.html

相关文章

  • Abp vNext 禁用数据库日志
    AbpvNext禁用数据库日志使用AbpvNext6.0在abp创建的数据库里有四张表是跟日志有关的AbpAuditLogs:审计日志,记录网络请求的AbpSecurityLogs:安全日志,记录登录日志的OpenIddictAuthorizations:OpenIddict记录登录操作的OpenIddictTokens:OpenIddict记录token的,access_token和......
  • Abp vNext自定义OpenIddict登录
    AbpvNext自定义OpenIdDict登录使用AbpvNext6.0我是打算给登录加一个验证码或者手机登录什么的,所以要自定义登录这方面官方文档写的不多,所以只能翻源码了源码分析首先就是去翻登录的api,用abp官方的angularDemo来看登录的路由,有三个网络请求/.well-known/openid-configurat......
  • ABP-VNext 用户权限管理系统实战02---用户权限表的创建与迁移
    一、表实体建立1、菜单表[Comment("菜单表")][Table("t_identity_menu")]publicclassMenu:AuditedAggregateRoot<Guid>,ISoftDelete,IMultiTenant{[MaxLength(200)][Comment("菜单名")]publicstringName{get;set;......
  • 2023-11-21 {“errcode”:40029,“errmsg”:“invalid code, rid: xxx”} ==》后端保
    今天上午登一下小程序,登录失败,后端调查发现是微信登录的时候报了这个错误:{“errcode”:40029,“errmsg”:“invalidcode,rid:xxx”}原因:后端保存的appsecret和appid与前端的appid不匹配导致。解决方案:更新后端保存的appsecret和appid即可。......
  • 【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret
    问题描述使用.NETAzure.Identity中的 DefaultAzureCredential 认证并连接到AzureKeyVault中,在KeyVault的示例中,并没有介绍如何在代码中设置连接到中国区Azure中。如果直接运行DefaultAzureCredential,会默认连接到GlobalAzure,那需要如何修改才能连接到ChinaAzure中呢? ......
  • 【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret
    问题描述使用.NETAzure.Identity中的 DefaultAzureCredential认证并连接到AzureKeyVault中,在KeyVault的示例中,并没有介绍如何在代码中设置连接到中国区Azure中。如果直接运行DefaultAzureCredential,会默认连接到GlobalAzure,那需要如何修改才能连接到ChinaAzure中呢......
  • secret-string-400
    打开是js代码  然后除了分析js代码的方法以外可以在浏览器的调试器直接对代码进行调试先找到主要的判断函数 然后我们找到loadcode()和run()函数进行分析没找到loadcode()函数那就分析run()函数加上调试代码进行调试console.log('new0pcode'+command.args) 然后在浏览......
  • Visual Studio中使用SourceLink调试Abp源码
    在VisualStudio中目前已经能轻松的调试Abp源码,只需要以下两步操作。【选项】中设置调试最重要的一步,Nuget安装SourceLink.Copy.PdbFiles库,启用SourceLink调试后未安装该库不会下载对应pdb文件启动调试,等待自动下载源码符号文件,然后就可以愉快的进行源码调试了。......
  • 基于ABP的AppUser对象扩展
     在ABP中AppUser表的数据字段是有限的,现在有个场景是和老系统用户对接,需要在AppUser表中添加一个UId和IMId字段。本文以AppUser表扩展UId和IMId字段为例进行介绍。一.在Abp默认解决方案Test.Identity.EntityFrameworkCore更改IdentityEfCoreEntityExtensionMappings类,该操作......
  • 在Abp.IO 框架上面加入JWT验证
    一.安装JWT所需的NuGet包 二.在WebModule.cs下配置在这个类库中找到ConfigureAuthentication这个方法    //授权认证(使用JWT)context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o=>{......