Skip to content

Commit eefc041

Browse files
committed
Intrinsify pbkdf2_hmac
1 parent f51c636 commit eefc041

File tree

3 files changed

+109
-1
lines changed

3 files changed

+109
-1
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/hashlib/HashlibModuleBuiltins.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@
4545
import static com.oracle.graal.python.nodes.BuiltinNames.J_SHA3;
4646
import static com.oracle.graal.python.nodes.BuiltinNames.T_HASHLIB;
4747
import static com.oracle.graal.python.nodes.BuiltinNames.T_SHA3;
48+
import static com.oracle.graal.python.nodes.ErrorMessages.ITERATION_VALUE_IS_TOO_GREAT;
49+
import static com.oracle.graal.python.nodes.ErrorMessages.ITERATION_VALUE_MUST_BE_GREATER_THAN_ZERO;
50+
import static com.oracle.graal.python.nodes.ErrorMessages.KEY_LENGTH_MUST_BE_GREATER_THAN_ZERO;
51+
import static com.oracle.graal.python.nodes.ErrorMessages.UNSUPPORTED_HASH_TYPE;
4852
import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING;
4953
import static com.oracle.graal.python.util.PythonUtils.toTruffleStringUncached;
5054
import static com.oracle.graal.python.util.PythonUtils.tsLiteral;
@@ -60,6 +64,12 @@
6064
import javax.crypto.Mac;
6165
import javax.crypto.spec.SecretKeySpec;
6266

67+
import org.bouncycastle.crypto.CipherParameters;
68+
import org.bouncycastle.crypto.Digest;
69+
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
70+
import org.bouncycastle.crypto.params.KeyParameter;
71+
import org.bouncycastle.jcajce.provider.util.DigestFactory;
72+
6373
import com.oracle.graal.python.PythonLanguage;
6474
import com.oracle.graal.python.annotations.ArgumentClinic;
6575
import com.oracle.graal.python.builtins.Builtin;
@@ -68,14 +78,17 @@
6878
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
6979
import com.oracle.graal.python.builtins.PythonBuiltins;
7080
import com.oracle.graal.python.builtins.modules.hashlib.HashlibModuleBuiltinsClinicProviders.NewNodeClinicProviderGen;
81+
import com.oracle.graal.python.builtins.modules.hashlib.HashlibModuleBuiltinsClinicProviders.Pbkdf2HmacNodeClinicProviderGen;
7182
import com.oracle.graal.python.builtins.objects.PNone;
7283
import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAccessLibrary;
7384
import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAcquireLibrary;
7485
import com.oracle.graal.python.builtins.objects.common.EconomicMapStorage;
7586
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes;
7687
import com.oracle.graal.python.builtins.objects.module.PythonModule;
7788
import com.oracle.graal.python.builtins.objects.ssl.CertUtils;
89+
import com.oracle.graal.python.lib.PyLongAsLongNode;
7890
import com.oracle.graal.python.nodes.ErrorMessages;
91+
import com.oracle.graal.python.nodes.PGuards;
7992
import com.oracle.graal.python.nodes.PRaiseNode;
8093
import com.oracle.graal.python.nodes.attributes.ReadAttributeFromPythonObjectNode;
8194
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
@@ -89,6 +102,7 @@
89102
import com.oracle.graal.python.nodes.util.CastToTruffleStringNode;
90103
import com.oracle.graal.python.runtime.IndirectCallData;
91104
import com.oracle.graal.python.runtime.object.PFactory;
105+
import com.oracle.truffle.api.CompilerDirectives;
92106
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
93107
import com.oracle.truffle.api.dsl.Bind;
94108
import com.oracle.truffle.api.dsl.Cached;
@@ -103,6 +117,7 @@
103117
import com.oracle.truffle.api.frame.VirtualFrame;
104118
import com.oracle.truffle.api.library.CachedLibrary;
105119
import com.oracle.truffle.api.nodes.Node;
120+
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
106121
import com.oracle.truffle.api.strings.TruffleString;
107122
import com.oracle.truffle.api.strings.TruffleString.CodeRange;
108123

@@ -451,4 +466,77 @@ static int getFips() {
451466
return 0;
452467
}
453468
}
469+
470+
@Builtin(name = "pbkdf2_hmac", minNumOfPositionalArgs = 4, parameterNames = {"hash_name", "password", "salt", "iterations", "dklen"})
471+
@GenerateNodeFactory
472+
@ArgumentClinic(name = "hash_name", conversion = ArgumentClinic.ClinicConversion.TString)
473+
@ArgumentClinic(name = "password", conversion = ArgumentClinic.ClinicConversion.ReadableBuffer)
474+
@ArgumentClinic(name = "salt", conversion = ArgumentClinic.ClinicConversion.ReadableBuffer)
475+
@ArgumentClinic(name = "iterations", conversion = ArgumentClinic.ClinicConversion.Long)
476+
abstract static class Pbkdf2HmacNode extends PythonClinicBuiltinNode {
477+
@Override
478+
protected ArgumentClinicProvider getArgumentClinic() {
479+
return Pbkdf2HmacNodeClinicProviderGen.INSTANCE;
480+
}
481+
482+
@Specialization(limit = "3")
483+
static Object pbkdf2(VirtualFrame frame, TruffleString hashName, Object password, Object salt, long iterations, Object dklenObj,
484+
@Bind("this") Node inliningTarget,
485+
@Bind PythonLanguage language,
486+
@CachedLibrary("password") PythonBufferAccessLibrary passwordLib,
487+
@CachedLibrary("salt") PythonBufferAccessLibrary saltLib,
488+
@Cached PyLongAsLongNode asLongNode,
489+
@Cached InlinedConditionProfile noDklenProfile,
490+
@Cached TruffleString.ToJavaStringNode toJavaStringNode,
491+
@Cached PRaiseNode raiseNode) {
492+
try {
493+
Digest digest = getDigest(toJavaStringNode.execute(hashName));
494+
if (digest == null) {
495+
throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.UnsupportedDigestmodError, UNSUPPORTED_HASH_TYPE, hashName);
496+
}
497+
if (iterations < 1) {
498+
throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.ValueError, ITERATION_VALUE_MUST_BE_GREATER_THAN_ZERO);
499+
}
500+
if (iterations > Integer.MAX_VALUE) {
501+
throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.OverflowError, ITERATION_VALUE_IS_TOO_GREAT);
502+
}
503+
long dklen;
504+
if (noDklenProfile.profile(inliningTarget, PGuards.isPNone(dklenObj))) {
505+
dklen = digest.getDigestSize();
506+
} else {
507+
dklen = asLongNode.execute(frame, inliningTarget, dklenObj);
508+
}
509+
dklen *= Byte.SIZE;
510+
if (dklen < 1) {
511+
throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.ValueError, KEY_LENGTH_MUST_BE_GREATER_THAN_ZERO);
512+
}
513+
if (dklen > Integer.MAX_VALUE) {
514+
throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.OverflowError, ITERATION_VALUE_IS_TOO_GREAT);
515+
}
516+
byte[] passwordBytes = passwordLib.getInternalOrCopiedExactByteArray(password);
517+
byte[] saltBytes = saltLib.getInternalOrCopiedExactByteArray(salt);
518+
return PFactory.createBytes(language, generate(digest, passwordBytes, saltBytes, (int) iterations, (int) dklen));
519+
} finally {
520+
passwordLib.release(password);
521+
saltLib.release(salt);
522+
}
523+
}
524+
525+
@TruffleBoundary
526+
private static Digest getDigest(String name) {
527+
name = name.toLowerCase();
528+
return DigestFactory.getDigest(NAME_MAPPINGS.getOrDefault(name, name));
529+
}
530+
531+
@TruffleBoundary
532+
private static byte[] generate(Digest digest, byte[] password, byte[] salt, int iterations, int dklen) {
533+
PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(digest);
534+
generator.init(password, salt, iterations);
535+
CipherParameters cipherParameters = generator.generateDerivedParameters(dklen);
536+
if (!(cipherParameters instanceof KeyParameter keyParameter)) {
537+
throw CompilerDirectives.shouldNotReachHere("unexpected cipher parameters");
538+
}
539+
return keyParameter.getKey();
540+
}
541+
}
454542
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/buffer/PythonBufferAccessLibrary.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -277,6 +277,21 @@ public final byte[] getInternalOrCopiedByteArray(Object receiver) {
277277
}
278278
}
279279

280+
/**
281+
* Get a byte array representing the buffer contents. Unlike
282+
* {@link #getInternalOrCopiedByteArray(Object)}, always returns a byte array with length equal
283+
* to the buffer size, making a copy if necessary. Do not write into the byte array.
284+
*/
285+
public final byte[] getInternalOrCopiedExactByteArray(Object receiver) {
286+
if (hasInternalByteArray(receiver)) {
287+
byte[] r = getInternalByteArray(receiver);
288+
if (r.length == getBufferLength(receiver)) {
289+
return r;
290+
}
291+
}
292+
return getCopiedByteArray(receiver);
293+
}
294+
280295
/**
281296
* Read a single byte from the buffer. Bounds checks are responsibility of the caller.
282297
*

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,10 @@ public abstract class ErrorMessages {
448448
public static final TruffleString ISINSTANCE_ARG_2_MUST_BE_TYPE_OR_TUPLE_OF_TYPE = tsLiteral("isinstance() arg 2 must be a type or tuple of types (was: %s)");
449449
public static final TruffleString ISSUBCLASS_MUST_BE_CLASS_OR_TUPLE = tsLiteral("issubclass() arg 2 must be a class or tuple of classes");
450450
public static final TruffleString ITER_V_MUST_BE_CALLABLE = tsLiteral("iter(v, w): v must be callable");
451+
public static final TruffleString ITERATION_VALUE_MUST_BE_GREATER_THAN_ZERO = tsLiteral("iteration value must be greater than 0.");
452+
public static final TruffleString ITERATION_VALUE_IS_TOO_GREAT = tsLiteral("iteration value is too great.");
453+
public static final TruffleString KEY_LENGTH_MUST_BE_GREATER_THAN_ZERO = tsLiteral("key length must be greater than 0.");
454+
public static final TruffleString KEY_LENGTH_IS_TOO_GREAT = tsLiteral("key length is too great.");
451455
public static final TruffleString KEYWORD_NAMES_MUST_BE_STR_GOT_P = tsLiteral("keyword names must be str, get %p");
452456
public static final TruffleString KEYWORDS_S_MUST_BE_STRINGS = tsLiteral("keywords must be strings");
453457
public static final TruffleString KLASS_ARG_IS_NOT_HOST_OBJ = tsLiteral("klass argument '%p' is not a host object");
@@ -777,6 +781,7 @@ public abstract class ErrorMessages {
777781
public static final TruffleString UNSIGNED_BYTE_INT_GREATER_THAN_MAX = tsLiteral("unsigned byte integer is greater than maximum");
778782
public static final TruffleString UNSIGNED_BYTE_INT_LESS_THAN_MIN = tsLiteral("unsigned byte integer is less than minimum");
779783
public static final TruffleString UNSUPPORTED_FORMAT_CHAR_AT_INDEX = tsLiteral("unsupported format character '%c' (0x%x) at index %d");
784+
public static final TruffleString UNSUPPORTED_HASH_TYPE = tsLiteral("unsupported hash type %s");
780785
public static final TruffleString UNSUPPORTED_INSTANCEOF = tsLiteral("unsupported instanceof(%p, %p)");
781786
public static final TruffleString UNSUPPORTED_LOCALE_SETTING = tsLiteral("unsupported locale setting");
782787
public static final TruffleString UNSUPPORTED_OBJ_IN = tsLiteral("unsupported object in '%s'");

0 commit comments

Comments
 (0)