未验证 提交 5d2dc941 编写于 作者: K Kevin Jones 提交者: GitHub

Support ChaChaPoly1305 on Android if available

In addition to making ChaCha/Poly work, this change refactors the "HasTag" implementation.

The CIPHER_HAS_TAG was used to determine if the GCMParameterSpec
configuration is needed for variable length tags.

While ChaCha20Poly1305 has authentication tags, it does not permit a tag
length other than 16 bytes, so there is nothing to configure.

This renames the CIPHER_HAS_TAG to be more specific that the cipher
supports more than one tag length, and removes it from ChaCha20Poly1305.

This simplifies the IV initialization a bit.
上级 d3e9a114
......@@ -135,6 +135,10 @@ internal static void CipherSetNonceLength(SafeEvpCipherCtxHandle ctx, int nonceL
SafeEvpCipherCtxHandle ctx,
int tagLength);
[DllImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_CipherIsSupported")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CipherIsSupported(IntPtr cipher);
[DllImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_Aes128Ecb")]
internal static extern IntPtr EvpAes128Ecb();
......@@ -216,6 +220,9 @@ internal static void CipherSetNonceLength(SafeEvpCipherCtxHandle ctx, int nonceL
[DllImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_RC2Ecb")]
internal static extern IntPtr EvpRC2Ecb();
[DllImport(Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_ChaCha20Poly1305")]
internal static extern IntPtr EvpChaCha20Poly1305();
internal enum EvpCipherDirection : int
{
NoChange = -1,
......
......@@ -7,7 +7,7 @@
enum
{
CIPHER_NONE = 0,
CIPHER_HAS_TAG = 1,
CIPHER_HAS_VARIABLE_TAG = 1,
CIPHER_REQUIRES_IV = 2,
};
typedef uint32_t CipherFlags;
......@@ -26,31 +26,32 @@ CipherInfo* AndroidCryptoNative_ ## cipherId() \
return &info; \
}
DEFINE_CIPHER(Aes128Ecb, 128, "AES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Aes128Cbc, 128, "AES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Cfb8, 128, "AES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Cfb128, 128, "AES/CFB128/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Gcm, 128, "AES/GCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Ccm, 128, "AES/CCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Ecb, 192, "AES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Aes192Cbc, 192, "AES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Cfb8, 192, "AES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Cfb128, 192, "AES/CFB128/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Gcm, 192, "AES/GCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Ccm, 192, "AES/CCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Ecb, 256, "AES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Aes256Cbc, 256, "AES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Cfb8, 256, "AES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Cfb128, 256, "AES/CFB128/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Gcm, 256, "AES/GCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Ccm, 256, "AES/CCM/NoPadding", CIPHER_HAS_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(DesEcb, 64, "DES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(DesCbc, 64, "DES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(DesCfb8, 64, "DES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Des3Ecb, 128, "DESede/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Des3Cbc, 128, "DESede/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Des3Cfb8, 128, "DESede/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Des3Cfb64, 128, "DESede/CFB/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Ecb, 128, "AES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Aes128Cbc, 128, "AES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Cfb8, 128, "AES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Cfb128, 128, "AES/CFB128/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Gcm, 128, "AES/GCM/NoPadding", CIPHER_HAS_VARIABLE_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes128Ccm, 128, "AES/CCM/NoPadding", CIPHER_HAS_VARIABLE_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Ecb, 192, "AES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Aes192Cbc, 192, "AES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Cfb8, 192, "AES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Cfb128, 192, "AES/CFB128/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Gcm, 192, "AES/GCM/NoPadding", CIPHER_HAS_VARIABLE_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes192Ccm, 192, "AES/CCM/NoPadding", CIPHER_HAS_VARIABLE_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Ecb, 256, "AES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Aes256Cbc, 256, "AES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Cfb8, 256, "AES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Cfb128, 256, "AES/CFB128/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Gcm, 256, "AES/GCM/NoPadding", CIPHER_HAS_VARIABLE_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Aes256Ccm, 256, "AES/CCM/NoPadding", CIPHER_HAS_VARIABLE_TAG | CIPHER_REQUIRES_IV)
DEFINE_CIPHER(DesEcb, 64, "DES/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(DesCbc, 64, "DES/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(DesCfb8, 64, "DES/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Des3Ecb, 128, "DESede/ECB/NoPadding", CIPHER_NONE)
DEFINE_CIPHER(Des3Cbc, 128, "DESede/CBC/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Des3Cfb8, 128, "DESede/CFB8/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(Des3Cfb64, 128, "DESede/CFB/NoPadding", CIPHER_REQUIRES_IV)
DEFINE_CIPHER(ChaCha20Poly1305, 256, "ChaCha20/Poly1305/NoPadding", CIPHER_REQUIRES_IV)
//
// We don't have to check whether `CipherInfo` arguments are valid pointers, as these functions will be called after the
......@@ -60,9 +61,9 @@ DEFINE_CIPHER(Des3Cfb64, 128, "DESede/CFB/NoPadding", CIPHER_REQUIRES_IV)
// The entry functions (those that can be called by external code) take care to validate that the context passed to them
// is a valid pointer and so we can assume the assertion from the preceding paragraph.
//
ARGS_NON_NULL_ALL static bool HasTag(CipherInfo* type)
ARGS_NON_NULL_ALL static bool HasVariableTag(CipherInfo* type)
{
return (type->flags & CIPHER_HAS_TAG) == CIPHER_HAS_TAG;
return (type->flags & CIPHER_HAS_VARIABLE_TAG) == CIPHER_HAS_VARIABLE_TAG;
}
ARGS_NON_NULL_ALL static bool RequiresIV(CipherInfo* type)
......@@ -75,6 +76,24 @@ ARGS_NON_NULL_ALL static jobject GetAlgorithmName(JNIEnv* env, CipherInfo* type)
return make_java_string(env, type->name);
}
int32_t AndroidCryptoNative_CipherIsSupported(CipherInfo* type)
{
abort_if_invalid_pointer_argument (type);
JNIEnv* env = GetJNIEnv();
jobject algName = GetAlgorithmName(env, type);
if (!algName)
return FAIL;
jobject cipher = (*env)->CallStaticObjectMethod(env, g_cipherClass, g_cipherGetInstanceMethod, algName);
(*env)->DeleteLocalRef(env, algName);
(*env)->DeleteLocalRef(env, cipher);
// If we were able to call Cipher.getInstance without an exception, like NoSuchAlgorithmException,
// then the algorithm is supported.
return TryClearJNIExceptions(env) ? FAIL : SUCCESS;
}
CipherCtx* AndroidCryptoNative_CipherCreatePartial(CipherInfo* type)
{
abort_if_invalid_pointer_argument (type);
......@@ -138,7 +157,8 @@ ARGS_NON_NULL_ALL static int32_t ReinitializeCipher(CipherCtx* ctx)
{
jbyteArray ivBytes = make_java_byte_array(env, ctx->ivLength);
(*env)->SetByteArrayRegion(env, ivBytes, 0, ctx->ivLength, (jbyte*)ctx->iv);
if (HasTag(ctx->type))
if (HasVariableTag(ctx->type))
{
ivPsObj = (*env)->NewObject(env, g_GCMParameterSpecClass, g_GCMParameterSpecCtor, ctx->tagLength * 8, ivBytes);
}
......
......@@ -24,6 +24,7 @@ typedef struct CipherCtx
uint8_t* iv;
} CipherCtx;
PALEXPORT int32_t AndroidCryptoNative_CipherIsSupported(CipherInfo* type);
PALEXPORT CipherCtx* AndroidCryptoNative_CipherCreate(CipherInfo* type, uint8_t* key, int32_t keySizeInBits, int32_t effectiveKeyLength, uint8_t* iv, int32_t enc);
PALEXPORT CipherCtx* AndroidCryptoNative_CipherCreatePartial(CipherInfo* type);
PALEXPORT int32_t AndroidCryptoNative_CipherSetTagLength(CipherCtx* ctx, int32_t tagLength);
......@@ -60,3 +61,4 @@ PALEXPORT CipherInfo* AndroidCryptoNative_Des3Cfb64(void);
PALEXPORT CipherInfo* AndroidCryptoNative_DesEcb(void);
PALEXPORT CipherInfo* AndroidCryptoNative_DesCfb8(void);
PALEXPORT CipherInfo* AndroidCryptoNative_DesCbc(void);
PALEXPORT CipherInfo* AndroidCryptoNative_ChaCha20Poly1305(void);
......@@ -697,7 +697,7 @@
<Compile Include="Internal\Cryptography\RC2Implementation.Android.cs" />
<Compile Include="System\Security\Cryptography\AesCcm.Android.cs" />
<Compile Include="System\Security\Cryptography\AesGcm.Android.cs" />
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.Android.cs" />
<Compile Include="System\Security\Cryptography\ECDiffieHellman.Create.Android.cs" />
<Compile Include="System\Security\Cryptography\ECDsa.Create.Android.cs" />
<Compile Include="System\Security\Cryptography\RSA.Create.Android.cs" />
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Win32.SafeHandles;
namespace System.Security.Cryptography
{
public sealed partial class ChaCha20Poly1305
{
private SafeEvpCipherCtxHandle _ctxHandle;
public static bool IsSupported { get; } = Interop.Crypto.CipherIsSupported(Interop.Crypto.EvpChaCha20Poly1305());
[MemberNotNull(nameof(_ctxHandle))]
private void ImportKey(ReadOnlySpan<byte> key)
{
// Constructors should check key size before calling ImportKey.
Debug.Assert(key.Length == KeySizeInBytes);
_ctxHandle = Interop.Crypto.EvpCipherCreatePartial(Interop.Crypto.EvpChaCha20Poly1305());
Interop.Crypto.CheckValidOpenSslHandle(_ctxHandle);
Interop.Crypto.EvpCipherSetKeyAndIV(
_ctxHandle,
key,
Span<byte>.Empty,
Interop.Crypto.EvpCipherDirection.NoChange);
Interop.Crypto.CipherSetNonceLength(_ctxHandle, NonceSizeInBytes);
}
private void EncryptCore(
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> plaintext,
Span<byte> ciphertext,
Span<byte> tag,
ReadOnlySpan<byte> associatedData = default)
{
Interop.Crypto.EvpCipherSetKeyAndIV(
_ctxHandle,
Span<byte>.Empty,
nonce,
Interop.Crypto.EvpCipherDirection.Encrypt);
if (!associatedData.IsEmpty)
{
Interop.Crypto.CipherUpdateAAD(_ctxHandle, associatedData);
}
byte[]? rented = null;
int ciphertextAndTagLength = checked(ciphertext.Length + tag.Length);
try
{
// Arbitrary limit.
const int StackAllocMax = 128;
Span<byte> ciphertextAndTag = stackalloc byte[StackAllocMax];
if (ciphertextAndTagLength > StackAllocMax)
{
rented = CryptoPool.Rent(ciphertextAndTagLength);
ciphertextAndTag = rented;
}
ciphertextAndTag = ciphertextAndTag.Slice(0, ciphertextAndTagLength);
if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, ciphertextAndTag, out int ciphertextBytesWritten, plaintext))
{
throw new CryptographicException();
}
if (!Interop.Crypto.EvpCipherFinalEx(
_ctxHandle,
ciphertextAndTag.Slice(ciphertextBytesWritten),
out int bytesWritten))
{
throw new CryptographicException();
}
ciphertextBytesWritten += bytesWritten;
// NOTE: Android appends tag to the end of the ciphertext in case of ChaCha20Poly1305 and "encryption" mode
if (ciphertextBytesWritten != ciphertextAndTagLength)
{
Debug.Fail($"ChaCha20Poly1305 encrypt wrote {ciphertextBytesWritten} of {ciphertextAndTagLength} bytes.");
throw new CryptographicException();
}
ciphertextAndTag.Slice(0, ciphertext.Length).CopyTo(ciphertext);
ciphertextAndTag.Slice(ciphertext.Length).CopyTo(tag);
}
finally
{
if (rented is not null)
{
CryptoPool.Return(rented, clearSize: ciphertextAndTagLength);
}
}
}
private void DecryptCore(
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> tag,
Span<byte> plaintext,
ReadOnlySpan<byte> associatedData)
{
Interop.Crypto.EvpCipherSetKeyAndIV(
_ctxHandle,
ReadOnlySpan<byte>.Empty,
nonce,
Interop.Crypto.EvpCipherDirection.Decrypt);
if (!associatedData.IsEmpty)
{
Interop.Crypto.CipherUpdateAAD(_ctxHandle, associatedData);
}
if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, plaintext, out int plaintextBytesWritten, ciphertext))
{
CryptographicOperations.ZeroMemory(plaintext);
throw new CryptographicException();
}
if (!Interop.Crypto.EvpCipherUpdate(_ctxHandle, plaintext.Slice(plaintextBytesWritten), out int bytesWritten, tag))
{
CryptographicOperations.ZeroMemory(plaintext);
throw new CryptographicException();
}
plaintextBytesWritten += bytesWritten;
if (!Interop.Crypto.EvpCipherFinalEx(
_ctxHandle,
plaintext.Slice(plaintextBytesWritten),
out bytesWritten))
{
CryptographicOperations.ZeroMemory(plaintext);
throw new CryptographicException(SR.Cryptography_AuthTagMismatch);
}
plaintextBytesWritten += bytesWritten;
if (plaintextBytesWritten != plaintext.Length)
{
Debug.Fail($"ChaCha20Poly1305 decrypt wrote {plaintextBytesWritten} of {plaintext.Length} bytes.");
throw new CryptographicException();
}
}
public void Dispose()
{
_ctxHandle.Dispose();
}
}
}
......@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Test.Cryptography;
using Xunit;
......@@ -447,6 +448,11 @@ public static void CheckIsSupported()
// The test queries the OS directly to ensure our version check is correct.
expectedIsSupported = CngUtility.IsAlgorithmSupported("CHACHA20_POLY1305");
}
else if (PlatformDetection.IsAndroid)
{
// Android with API Level 28 is the minimum API Level support for ChaChaPoly1305.
expectedIsSupported = GetAndroidSdkVersion() >= 28;
}
else if (PlatformDetection.OpenSslPresentOnSystem &&
(PlatformDetection.IsOSX || PlatformDetection.IsOpenSslSupported))
{
......@@ -456,5 +462,22 @@ public static void CheckIsSupported()
Assert.Equal(expectedIsSupported, ChaCha20Poly1305.IsSupported);
}
private static int GetAndroidSdkVersion()
{
using Process proc = new Process();
proc.StartInfo.FileName = "getprop";
proc.StartInfo.Arguments = " ro.build.version.sdk";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
string stdout = proc.StandardOutput.ReadToEnd();
// This should never take more than a second.
int sdkVersion = -1;
bool success = proc.WaitForExit(5_000) && int.TryParse(stdout, out sdkVersion);
Assert.True(success, "Could not determine Android SDK version for current device.");
return sdkVersion;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册