Ich möchte JWTs erstellen und überprüfen

Mit der JWT-Bibliothek von Tink können JSON Web Tokens (JWTs) erstellt und überprüft werden.

JWTs erstellen

Wir empfehlen die JwtPublicKeySign-Primitiven mit dem Schlüsseltyp JWT_ES256 für die meisten Anwendungsfälle.

Die folgenden Beispiele zeigen, wie JWTs erstellt und wie das öffentliche Keyset in das JWK-Set-Format konvertiert wird.

C++

// An example for signing JSON Web Tokens (JWT).
#include <iostream>
#include <memory>
#include <ostream>
#include <string>
#include <utility>

#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/log/check.h"
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "tink/config/global_registry.h"
#include "util/util.h"
#include "tink/jwt/jwt_public_key_sign.h"
#include "tink/jwt/jwt_signature_config.h"
#include "tink/jwt/raw_jwt.h"
#include "tink/keyset_handle.h"
#include "tink/util/status.h"

ABSL_FLAG(std::string, keyset_filename, "", "Keyset file in JSON format");
ABSL_FLAG(std::string, audience, "", "Expected audience in the token");
ABSL_FLAG(std::string, token_filename, "", "Path to the token file");

namespace {

using ::crypto::tink::JwtPublicKeySign;
using ::crypto::tink::KeysetHandle;
using ::crypto::tink::RawJwt;
using ::crypto::tink::RawJwtBuilder;
using ::crypto::tink::util::Status;
using ::crypto::tink::util::StatusOr;

void ValidateParams() {
  // ...
}

}  // namespace

namespace tink_cc_examples {

// JWT sign example CLI implementation.
Status JwtSign(const std::string& keyset_filename, absl::string_view audience,
               const std::string& token_filename) {
  Status result = crypto::tink::JwtSignatureRegister();
  if (!result.ok()) return result;

  // Read the keyset from file.
  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
      ReadJsonCleartextKeyset(keyset_filename);
  if (!keyset_handle.ok()) return keyset_handle.status();
  StatusOr<RawJwt> raw_jwt =
      RawJwtBuilder()
          .AddAudience(audience)
          .SetExpiration(absl::Now() + absl::Seconds(100))
          .Build();
  if (!raw_jwt.ok()) return raw_jwt.status();
  StatusOr<std::unique_ptr<JwtPublicKeySign>> jwt_signer =
      (*keyset_handle)
          ->GetPrimitive<crypto::tink::JwtPublicKeySign>(
              crypto::tink::ConfigGlobalRegistry());
  if (!jwt_signer.ok()) return jwt_signer.status();

  StatusOr<std::string> token = (*jwt_signer)->SignAndEncode(*raw_jwt);
  if (!token.ok()) return token.status();

  return WriteToFile(*token, token_filename);
}

}  // namespace tink_cc_examples

int main(int argc, char** argv) {
  absl::ParseCommandLine(argc, argv);

  ValidateParams();

  std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename);
  std::string audience = absl::GetFlag(FLAGS_audience);
  std::string token_filename = absl::GetFlag(FLAGS_token_filename);

  std::clog << "Using keyset in " << keyset_filename << " to ";
  std::clog << " generate and sign a token using audience '" << audience
            << "'; the resulting signature is written to " << token_filename
            << '\n';

  CHECK_OK(
      tink_cc_examples::JwtSign(keyset_filename, audience, token_filename));
  return 0;
}

Go

func Example_signAndVerify() {
	// A private keyset created with
	// "tinkey create-keyset --key-template=JWT_ES256 --out private_keyset.cfg".
	// Note that this keyset has the secret key information in cleartext.
	privateJSONKeyset := `{
		"primaryKeyId": 1742360595,
		"key": [
			{
				"keyData": {
					"typeUrl": "type.googleapis.com/google.crypto.tink.JwtEcdsaPrivateKey",
					"value": "GiBgVYdAPg3Fa2FVFymGDYrI1trHMzVjhVNEMpIxG7t0HRJGIiBeoDMF9LS5BDCh6YgqE3DjHwWwnEKEI3WpPf8izEx1rRogbjQTXrTcw/1HKiiZm2Hqv41w7Vd44M9koyY/+VsP+SAQAQ==",
					"keyMaterialType": "ASYMMETRIC_PRIVATE"
				},
				"status": "ENABLED",
				"keyId": 1742360595,
				"outputPrefixType": "TINK"
			}
		]
	}`

	// The corresponding public keyset created with
	// "tinkey create-public-keyset --in private_keyset.cfg"
	publicJSONKeyset := `{
		"primaryKeyId": 1742360595,
		"key": [
			{
				"keyData": {
					"typeUrl": "type.googleapis.com/google.crypto.tink.JwtEcdsaPublicKey",
					"value": "EAEaIG40E1603MP9RyoomZth6r+NcO1XeODPZKMmP/lbD/kgIiBeoDMF9LS5BDCh6YgqE3DjHwWwnEKEI3WpPf8izEx1rQ==",
					"keyMaterialType": "ASYMMETRIC_PUBLIC"
				},
				"status": "ENABLED",
				"keyId": 1742360595,
				"outputPrefixType": "TINK"
			}
		]
	}`

	// Create a keyset handle from the cleartext private keyset in the previous
	// step. The keyset handle provides abstract access to the underlying keyset to
	// limit the access of the raw key material. WARNING: In practice,
	// it is unlikely you will want to use a insecurecleartextkeyset, as it implies
	// that your key material is passed in cleartext, which is a security risk.
	// Consider encrypting it with a remote key in Cloud KMS, AWS KMS or HashiCorp Vault.
	// See https://github.com/google/tink/blob/master/docs/GOLANG-HOWTO.md#storing-and-loading-existing-keysets.
	privateKeysetHandle, err := insecurecleartextkeyset.Read(
		keyset.NewJSONReader(bytes.NewBufferString(privateJSONKeyset)))
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve the JWT Signer primitive from privateKeysetHandle.
	signer, err := jwt.NewSigner(privateKeysetHandle)
	if err != nil {
		log.Fatal(err)
	}

	// Use the primitive to create and sign a token. In this case, the primary key of the
	// keyset will be used (which is also the only key in this example).
	expiresAt := time.Now().Add(time.Hour)
	audience := "example audience"
	subject := "example subject"
	rawJWT, err := jwt.NewRawJWT(&jwt.RawJWTOptions{
		Audience:  &audience,
		Subject:   &subject,
		ExpiresAt: &expiresAt,
	})
	if err != nil {
		log.Fatal(err)
	}
	token, err := signer.SignAndEncode(rawJWT)
	if err != nil {
		log.Fatal(err)
	}

	// Create a keyset handle from the keyset containing the public key. Because the
	// public keyset does not contain any secrets, we can use [keyset.ReadWithNoSecrets].
	publicKeysetHandle, err := keyset.ReadWithNoSecrets(
		keyset.NewJSONReader(bytes.NewBufferString(publicJSONKeyset)))
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve the Verifier primitive from publicKeysetHandle.
	verifier, err := jwt.NewVerifier(publicKeysetHandle)
	if err != nil {
		log.Fatal(err)
	}

	// Verify the signed token.
	validator, err := jwt.NewValidator(&jwt.ValidatorOpts{ExpectedAudience: &audience})
	if err != nil {
		log.Fatal(err)
	}
	verifiedJWT, err := verifier.VerifyAndDecode(token, validator)
	if err != nil {
		log.Fatal(err)
	}

	// Extract subject claim from the token.
	if !verifiedJWT.HasSubject() {
		log.Fatal(err)
	}
	extractedSubject, err := verifiedJWT.Subject()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(extractedSubject)
	// Output: example subject
}

Java

package jwt;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.RegistryConfiguration;
import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
import com.google.crypto.tink.jwt.JwtPublicKeySign;
import com.google.crypto.tink.jwt.JwtSignatureConfig;
import com.google.crypto.tink.jwt.RawJwt;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;

/**
 * A command-line utility for signing JSON Web Tokens (JWTs).
 *
 * <p>It loads cleartext private keys from disk - this is not recommended!
 *
 * <p>It requires the following arguments:
 *
 * <ul>
 *   <li>private-keyset-file: Name of the input file containing the private keyset.
 *   <li>audience: The audience claim to be used in the token
 *   <li>token-file: name of the output file containing the signed JWT.
 */
public final class JwtSign {
  public static void main(String[] args) throws Exception {
    if (args.length != 3) {
      System.err.printf("Expected 3 parameters, got %d\n", args.length);
      System.err.println("Usage: java JwtSign private-keyset-file audience token-file");
      System.exit(1);
    }

    Path privateKeysetFile = Paths.get(args[0]);
    String audience = args[1];
    Path tokenFile = Paths.get(args[2]);

    // Register all JWT signature key types with the Tink runtime.
    JwtSignatureConfig.register();

    // Read the private keyset into a KeysetHandle.
    KeysetHandle privateKeysetHandle =
        TinkJsonProtoKeysetFormat.parseKeyset(
            new String(Files.readAllBytes(privateKeysetFile), UTF_8),
            InsecureSecretKeyAccess.get());

    // Get the primitive.
    JwtPublicKeySign signer =
        privateKeysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeySign.class);

    // Use the primitive to sign a token that expires in 100 seconds.
    RawJwt rawJwt =
        RawJwt.newBuilder()
            .addAudience(audience)
            .setExpiration(Instant.now().plusSeconds(100))
            .build();
    String signedToken = signer.signAndEncode(rawJwt);
    Files.write(tokenFile, signedToken.getBytes(UTF_8));
  }

  private JwtSign() {}
}

Python

"""A utility for creating and signing JSON Web Tokens (JWT).

It loads cleartext keys from disk - this is not recommended!
"""

import datetime

from absl import app
from absl import flags
from absl import logging
import tink
from tink import jwt
from tink import secret_key_access


_PRIVATE_KEYSET_PATH = flags.DEFINE_string(
    'private_keyset_path', None,
    'Path to the keyset used for the JWT signature operation.')
_AUDIENCE = flags.DEFINE_string('audience', None,
                                'Audience to be used in the token')
_TOKEN_PATH = flags.DEFINE_string('token_path', None, 'Path to the token file.')


def main(argv):
  del argv  # Unused.

  # Initialise Tink
  jwt.register_jwt_signature()

  # Read the keyset into a KeysetHandle
  with open(_PRIVATE_KEYSET_PATH.value, 'rt') as keyset_file:
    try:
      text = keyset_file.read()
      keyset_handle = tink.json_proto_keyset_format.parse(
          text, secret_key_access.TOKEN
      )
    except tink.TinkError as e:
      logging.exception('Error reading keyset: %s', e)
      return 1

  now = datetime.datetime.now(tz=datetime.timezone.utc)

  # Get the JwtPublicKeySign primitive
  try:
    jwt_sign = keyset_handle.primitive(jwt.JwtPublicKeySign)
  except tink.TinkError as e:
    logging.exception('Error creating JwtPublicKeySign: %s', e)
    return 1

  # Create token
  raw_jwt = jwt.new_raw_jwt(
      audiences=[_AUDIENCE.value],
      expiration=now + datetime.timedelta(seconds=100))
  token = jwt_sign.sign_and_encode(raw_jwt)
  with open(_TOKEN_PATH.value, 'wt') as token_file:
    token_file.write(token)
  logging.info('Token has been written to %s', _TOKEN_PATH.value)


if __name__ == '__main__':
  flags.mark_flags_as_required(
      ['private_keyset_path', 'audience', 'token_path'])
  app.run(main)

Eine gängige Methode zum Teilen des öffentlichen Schlüsselsatzes ist das JWK-Set-Format. Das folgende Beispiel zeigt, wie das öffentliche Keyset in das JWK-Set-Format konvertiert wird.

C++

// An example for converting a Tink keyset with public keys into a JWK set.
#include <iostream>
#include <memory>
#include <ostream>
#include <string>

#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/log/check.h"
#include "util/util.h"
#include "tink/jwt/jwk_set_converter.h"
#include "tink/jwt/jwt_signature_config.h"
#include "tink/keyset_handle.h"
#include "tink/util/status.h"

ABSL_FLAG(std::string, public_keyset_filename, "",
          "Public keyset file in Tink's JSON format");
ABSL_FLAG(std::string, public_jwk_set_filename, "",
          "Path to the output public JWK set file");

namespace {

using ::crypto::tink::JwkSetFromPublicKeysetHandle;
using ::crypto::tink::KeysetHandle;
using ::crypto::tink::util::Status;
using ::crypto::tink::util::StatusOr;

void ValidateParams() {
  // ...
}

}  // namespace

namespace tink_cc_examples {

Status JwtGeneratePublicJwkSet(const std::string& public_keyset_filename,
                               const std::string& public_jwk_set_filename) {
  Status result = crypto::tink::JwtSignatureRegister();
  if (!result.ok()) return result;

  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
      ReadJsonCleartextKeyset(public_keyset_filename);
  if (!keyset_handle.ok()) return keyset_handle.status();

  StatusOr<std::string> public_jwk_set =
      JwkSetFromPublicKeysetHandle(**keyset_handle);
  if (!public_jwk_set.ok()) return keyset_handle.status();

  return WriteToFile(*public_jwk_set, public_jwk_set_filename);
}

}  // namespace tink_cc_examples

int main(int argc, char** argv) {
  absl::ParseCommandLine(argc, argv);

  ValidateParams();

  std::string public_keyset_filename =
      absl::GetFlag(FLAGS_public_keyset_filename);
  std::string public_jwk_set_filename =
      absl::GetFlag(FLAGS_public_jwk_set_filename);

  std::clog << "Convert public keyset in " << public_keyset_filename << " to ";
  std::clog << " to JWK set format; the result is written to "
            << public_jwk_set_filename << '\n';

  CHECK_OK(tink_cc_examples::JwtGeneratePublicJwkSet(public_keyset_filename,
                                                     public_jwk_set_filename));
  return 0;
}

Go

func Example_generateJWKS() {
	// A Tink keyset in JSON format with one JWT public key.
	publicJSONKeyset := `{
		"primaryKeyId": 1742360595,
		"key": [
			{
				"keyData": {
					"typeUrl": "type.googleapis.com/google.crypto.tink.JwtEcdsaPublicKey",
					"value": "EAEaIG40E1603MP9RyoomZth6r+NcO1XeODPZKMmP/lbD/kgIiBeoDMF9LS5BDCh6YgqE3DjHwWwnEKEI3WpPf8izEx1rQ==",
					"keyMaterialType": "ASYMMETRIC_PUBLIC"
				},
				"status": "ENABLED",
				"keyId": 1742360595,
				"outputPrefixType": "TINK"
			}
		]
	}`

	// Create a keyset handle from the keyset containing the public key. Because the
	// public keyset does not contain any secrets, we can use [keyset.ReadWithNoSecrets].
	publicKeysetHandle, err := keyset.ReadWithNoSecrets(
		keyset.NewJSONReader(bytes.NewBufferString(publicJSONKeyset)))
	if err != nil {
		log.Fatal(err)
	}

	// Create a publicJWKset from publicKeysetHandle.
	publicJWKset, err := jwt.JWKSetFromPublicKeysetHandle(publicKeysetHandle)
	if err != nil {
		log.Fatal(err)
	}

	// Remove whitespace so that we can compare it to the expected string.
	compactPublicJWKset := &bytes.Buffer{}
	err = json.Compact(compactPublicJWKset, publicJWKset)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(compactPublicJWKset.String())
	// Output:
	// {"keys":[{"alg":"ES256","crv":"P-256","key_ops":["verify"],"kid":"Z9pQEw","kty":"EC","use":"sig","x":"bjQTXrTcw_1HKiiZm2Hqv41w7Vd44M9koyY_-VsP-SA","y":"XqAzBfS0uQQwoemIKhNw4x8FsJxChCN1qT3_IsxMda0"}]}
}

Java

package jwt;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.crypto.tink.InsecureSecretKeyAccess;
import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
import com.google.crypto.tink.jwt.JwkSetConverter;
import com.google.crypto.tink.jwt.JwtSignatureConfig;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * A command-line example for generating the public JWT keyset in JWK set format.
 *
 * <p>It loads cleartext private keys from disk - this is not recommended!
 *
 * <p>It requires the following arguments:
 *
 * <ul>
 *   <li>private-keyset-file: Name of the input file containing the private keyset.
 *   <li>public-jwkset-file: Name of the output file containing the public key in JWK set format.
 */
public final class JwtGeneratePublicJwkSet {
  public static void main(String[] args) throws Exception {
    if (args.length != 2) {
      System.err.printf("Expected 2 parameters, got %d\n", args.length);
      System.err.println(
          "Usage: java JwtGeneratePublicJwkSet private-keyset-file public-jwk-set-file");
      System.exit(1);
    }

    Path privateKeysetFile = Paths.get(args[0]);
    Path publicJwkSetFile = Paths.get(args[1]);

    // Register all JWT signature key types with the Tink runtime.
    JwtSignatureConfig.register();

    // Read the keyset into a KeysetHandle.
    KeysetHandle privateKeysetHandle =
        TinkJsonProtoKeysetFormat.parseKeyset(
            new String(Files.readAllBytes(privateKeysetFile), UTF_8),
            InsecureSecretKeyAccess.get());

    // Export the public keyset as JWK set.
    String publicJwkSet =
        JwkSetConverter.fromPublicKeysetHandle(privateKeysetHandle.getPublicKeysetHandle());
    Files.write(publicJwkSetFile, publicJwkSet.getBytes(UTF_8));
  }

  private JwtGeneratePublicJwkSet() {}
}

Python

"""A utility for generating the public JWK set from the public keyset.
"""

from absl import app
from absl import flags
from absl import logging
import tink
from tink import jwt


_PUBLIC_KEYSET_PATH = flags.DEFINE_string(
    'public_keyset_path', None,
    'Path to the public keyset in Tink JSON format.')
_PUBLIC_JWK_SET_PATH = flags.DEFINE_string(
    'public_jwk_set_path', None, 'Path to public keyset in JWK format.')


def main(argv):
  del argv  # Unused.

  # Initialise Tink
  jwt.register_jwt_signature()

  # Read the keyset into a KeysetHandle
  with open(_PUBLIC_KEYSET_PATH.value, 'rt') as keyset_file:
    try:
      text = keyset_file.read()
      public_keyset_handle = tink.json_proto_keyset_format.parse_without_secret(
          text
      )
    except tink.TinkError as e:
      logging.exception('Error reading keyset: %s', e)
      return 1

  # Export Public Keyset as JWK set
  public_jwk_set = jwt.jwk_set_from_public_keyset_handle(public_keyset_handle)
  with open(_PUBLIC_JWK_SET_PATH.value, 'wt') as public_jwk_set_file:
    public_jwk_set_file.write(public_jwk_set)
  logging.info('The public JWK set has been written to %s',
               _PUBLIC_JWK_SET_PATH.value)


if __name__ == '__main__':
  flags.mark_flags_as_required(['public_keyset_path', 'public_jwk_set_path'])
  app.run(main)

JWTs überprüfen

Wir empfehlen die JwtPublicKeyVerify-Primitiven mit dem Schlüsseltyp JWT_ES256 für die meisten Anwendungsfälle.

Die folgenden Beispiele zeigen, wie JWTs mit einem öffentlichen Schlüssel-Set im JWK-Set-Format verifiziert werden. Dieses Format wird häufig verwendet, um öffentliche Schlüssel-Sets mit anderen Parteien zu teilen.

C++

// A utility for creating, signing and verifying JSON Web Tokens (JWT).
#include <iostream>
#include <memory>
#include <ostream>
#include <string>
#include <utility>

#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/log/check.h"
#include "absl/strings/string_view.h"
#include "tink/config/global_registry.h"
#include "util/util.h"
#include "tink/jwt/jwk_set_converter.h"
#include "tink/jwt/jwt_public_key_verify.h"
#include "tink/jwt/jwt_signature_config.h"
#include "tink/jwt/jwt_validator.h"
#include "tink/keyset_handle.h"
#include "tink/util/status.h"

ABSL_FLAG(std::string, jwk_set_filename, "", "Path to the JWK set file");
ABSL_FLAG(std::string, audience, "", "Expected audience in the token");
ABSL_FLAG(std::string, token_filename, "", "Path to the token file");

namespace {

using ::crypto::tink::JwkSetToPublicKeysetHandle;
using ::crypto::tink::JwtPublicKeyVerify;
using ::crypto::tink::JwtValidator;
using ::crypto::tink::KeysetHandle;
using ::crypto::tink::util::Status;
using ::crypto::tink::util::StatusOr;

void ValidateParams() {
  // ...
}

}  // namespace

namespace tink_cc_examples {

// JWT verify example CLI implementation.
Status JwtVerify(const std::string& jwk_set_filename,
                 absl::string_view audience,
                 const std::string& token_filename) {
  Status result = crypto::tink::JwtSignatureRegister();
  if (!result.ok()) return result;

  // Read the JWK set from file and convert it.
  StatusOr<std::string> jwk_set = ReadFile(jwk_set_filename);
  if (!jwk_set.ok()) return jwk_set.status();
  StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
      JwkSetToPublicKeysetHandle(*jwk_set);

  // Read the token.
  StatusOr<std::string> token = ReadFile(token_filename);
  if (!token.ok()) return token.status();

  StatusOr<JwtValidator> validator =
      crypto::tink::JwtValidatorBuilder().ExpectAudience(audience).Build();
  if (!validator.ok()) return validator.status();

  StatusOr<std::unique_ptr<JwtPublicKeyVerify>> jwt_verifier =
      (*keyset_handle)
          ->GetPrimitive<crypto::tink::JwtPublicKeyVerify>(
              crypto::tink::ConfigGlobalRegistry());
  if (!jwt_verifier.ok()) return jwt_verifier.status();

  return (*jwt_verifier)->VerifyAndDecode(*token, *validator).status();
}

}  // namespace tink_cc_examples

int main(int argc, char** argv) {
  absl::ParseCommandLine(argc, argv);

  ValidateParams();

  std::string jwk_set_filename = absl::GetFlag(FLAGS_jwk_set_filename);
  std::string audience = absl::GetFlag(FLAGS_audience);
  std::string token_filename = absl::GetFlag(FLAGS_token_filename);

  std::clog << "Using keyset in " << jwk_set_filename << " to ";
  std::clog << " verify a token with expected audience '" << audience << '\n';

  CHECK_OK(
      tink_cc_examples::JwtVerify(jwk_set_filename, audience, token_filename));
  return 0;
}

Go

func Example_verifyWithJWKS() {
	// A signed token with the subject 'example subject', audience 'example audience'.
	// and expiration on 2023-03-23.
	token := `eyJhbGciOiJFUzI1NiIsICJraWQiOiJaOXBRRXcifQ.eyJhdWQiOiJleGFtcGxlIGF1ZGllbmNlIiwgImV4cCI6MTY3OTUzMzIwMCwgInN1YiI6ImV4YW1wbGUgc3ViamVjdCJ9.ZvI0T84fJ1aouiB7n62kHOmbm0Hpfiz0JtYs15XVDT8KyoVYZ8hu_DGJUN47BqZIbuOI-rdu9TxJvutj8uF3Ow`

	// A public keyset in the JWK set format.
	publicJWKset := `{
		"keys":[
			{
				"alg":"ES256",
				"crv":"P-256",
				"key_ops":["verify"],
				"kid":"Z9pQEw",
				"kty":"EC",
				"use":"sig",
				"x":"bjQTXrTcw_1HKiiZm2Hqv41w7Vd44M9koyY_-VsP-SA",
				"y":"XqAzBfS0uQQwoemIKhNw4x8FsJxChCN1qT3_IsxMda0"
			}
		]
	}`

	// Create a keyset handle from publicJWKset.
	publicKeysetHandle, err := jwt.JWKSetToPublicKeysetHandle([]byte(publicJWKset))
	if err != nil {
		log.Fatal(err)
	}

	// Retrieve the Verifier primitive from publicKeysetHandle.
	verifier, err := jwt.NewVerifier(publicKeysetHandle)
	if err != nil {
		log.Fatal(err)
	}

	// Verify the signed token. For this example, we use a fixed date. Usually, you would
	// either not set FixedNow, or set it to the current time.
	audience := "example audience"
	validator, err := jwt.NewValidator(&jwt.ValidatorOpts{
		ExpectedAudience: &audience,
		FixedNow:         time.Date(2023, 3, 23, 0, 0, 0, 0, time.UTC),
	})
	if err != nil {
		log.Fatal(err)
	}
	verifiedJWT, err := verifier.VerifyAndDecode(token, validator)
	if err != nil {
		log.Fatal(err)
	}

	// Extract subject claim from the token.
	if !verifiedJWT.HasSubject() {
		log.Fatal(err)
	}
	extractedSubject, err := verifiedJWT.Subject()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(extractedSubject)
	// Output: example subject
}

Java

package jwt;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.crypto.tink.KeysetHandle;
import com.google.crypto.tink.RegistryConfiguration;
import com.google.crypto.tink.jwt.JwkSetConverter;
import com.google.crypto.tink.jwt.JwtPublicKeyVerify;
import com.google.crypto.tink.jwt.JwtSignatureConfig;
import com.google.crypto.tink.jwt.JwtValidator;
import com.google.crypto.tink.jwt.VerifiedJwt;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;

/**
 * A command-line utility for verifying JSON Web Tokens (JWTs).
 *
 * <p>It requires the following arguments:
 *
 * <ul>
 *   <li>public-jwkset-file: Name of the input file containing the public keyset in JWK set format.
 *   <li>audience: The audience claim to be used in the token
 *   <li>token-file: name of the input file containing the signed JWT.
 */
public final class JwtVerify {
  public static void main(String[] args) throws Exception {
    if (args.length != 3) {
      System.err.printf("Expected 3 parameters, got %d\n", args.length);
      System.err.println(
          "Usage: java JwtVerify public-jwk-set-file audience token-file");
      System.exit(1);
    }

    Path publicJwkSetFile = Paths.get(args[0]);
    String audience = args[1];
    Path tokenFile = Paths.get(args[2]);

    // Register all JWT signature key types with the Tink runtime.
    JwtSignatureConfig.register();

    // Read the public keyset in JWK set format into a KeysetHandle.
    KeysetHandle publicKeysetHandle =
        JwkSetConverter.toPublicKeysetHandle(
            new String(Files.readAllBytes(publicJwkSetFile), UTF_8));

    List<String> lines = Files.readAllLines(tokenFile, UTF_8);
    if (lines.size() != 1) {
      System.err.printf("The signature file should contain only one line,  got %d", lines.size());
      System.exit(1);
    }
    String signedToken = lines.get(0).trim();

    // Get the primitive.
    JwtPublicKeyVerify verifier =
        publicKeysetHandle.getPrimitive(RegistryConfiguration.get(), JwtPublicKeyVerify.class);

    // Use the primitive to verify a token.
    JwtValidator validator = JwtValidator.newBuilder().expectAudience(audience).build();
    VerifiedJwt verifiedJwt = verifier.verifyAndDecode(signedToken, validator);
    long seconds = ChronoUnit.SECONDS.between(Instant.now(), verifiedJwt.getExpiration());
    System.out.println("Token is valid and expires in " + seconds + " seconds.");
  }

  private JwtVerify() {}
}

Python

"""A utility for verifying Json Web Tokens (JWT)."""

import datetime

from absl import app
from absl import flags
from absl import logging
import tink
from tink import jwt


FLAGS = flags.FLAGS

_PUBLIC_JWK_SET_PATH = flags.DEFINE_string(
    'public_jwk_set_path', None, 'Path to public keyset in JWK set format.')
_AUDIENCE = flags.DEFINE_string('audience', None,
                                'Audience to be used in the token')
_TOKEN_PATH = flags.DEFINE_string('token_path', None,
                                  'Path to the signature file.')


def main(argv):
  del argv  # Unused.

  # Initialise Tink
  jwt.register_jwt_signature()

  with open(_PUBLIC_JWK_SET_PATH.value, 'rt') as public_jwk_set_file:
    try:
      text = public_jwk_set_file.read()
      keyset_handle = jwt.jwk_set_to_public_keyset_handle(text)
    except tink.TinkError as e:
      logging.exception('Error reading public JWK set: %s', e)
      return 1

  now = datetime.datetime.now(tz=datetime.timezone.utc)
  try:
    jwt_verify = keyset_handle.primitive(jwt.JwtPublicKeyVerify)
  except tink.TinkError as e:
    logging.exception('Error creating JwtPublicKeyVerify: %s', e)
    return 1

  # Verify token
  with open(_TOKEN_PATH.value, 'rt') as token_file:
    token = token_file.read()
  validator = jwt.new_validator(expected_audience=_AUDIENCE.value)
  try:
    verified_jwt = jwt_verify.verify_and_decode(token, validator)
    expires_in = verified_jwt.expiration() - now
    logging.info('Token is valid and expires in %s seconds', expires_in.seconds)
    return 0
  except tink.TinkError as e:
    logging.info('JWT verification failed: %s', e)
    return 1


if __name__ == '__main__':
  flags.mark_flags_as_required(['audience', 'token_path'])
  app.run(main)

JSON-Web-Tokens (JWTs)

Tink unterstützt das Generieren und Überprüfen von JWTs, die im Web weit verbreitet sind. Die JWT-Implementierung von Tink bietet eine Teilmenge des in RFC 7519 definierten JWT-Standards, die das Tink-Team für sicher hält und die gut in die Tink-Bibliothek passt.

Tink unterstützt keine Teile des Standards, die selten verwendet werden oder schwer richtig zu verwenden sind. Es gelten folgende Einschränkungen:

  • Tink unterstützt nur das Format JWS Compact Serialization. Die JWS-JSON-Serialisierung und JWE werden nicht unterstützt.
  • Tink unterstützt den Wert None im Header alg nicht.
  • Tink unterstützt nur die Header typ, alg und kid. Alle anderen Header werden nicht unterstützt.
  • Mit Tink können Tokens erst geparst werden, wenn die Signatur oder der MAC überprüft wurde.

JWT-Signaturen

Wenn Tokens von verschiedenen Einheiten generiert und überprüft werden, sollten Sie asymmetrische Schlüssel mit den Primitiven JwtPublicKeySign und JwtPublicKeyVerify verwenden. Der private Schlüssel wird zum Generieren von Tokens verwendet und der öffentliche Schlüssel zum Überprüfen von Tokens. Die von diesen Primitiven unterstützten Algorithmen sind: ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384 und PS512.

Schlüsseltyp auswählen

Für JWT-Signaturen werden andere Schlüsseltypen als für die normale digitale Signatur in Tink verwendet. Das ist erforderlich, da einige Metadaten (z. B. alg und kid) mit dem Schlüssel gespeichert werden müssen.

Wir empfehlen, JWT_ES256 für die meisten Anwendungsfälle zu verwenden. Mit diesem Schlüsseltyp generierte Tokens haben immer einen kid-Header. Wenn Sie etwas kürzere Tokens ohne kid-Header bevorzugen, wählen Sie den Schlüsseltyp JWT_ES256_RAW aus. Eine Liste aller unterstützten Schlüsseltypen finden Sie unter Unterstützte Schlüsseltypen.

Verteilung von öffentlichen Schlüsselsätzen

Mit Tink können öffentliche Schlüsselsätze in das in RFC 7517 definierte JWK-Set-Format konvertiert werden, das von den meisten JWT-Bibliotheken unterstützt wird.

Tink unterstützt den Export öffentlicher JWT-Schlüssel in keinem anderen Format. Der Grund dafür ist, dass andere Formate nicht die Metadaten alg und kid enthalten, die für die Bestätigung verwendet werden. Dadurch ist die Verwendung fehleranfälliger und es kann schwieriger sein, Schlüssel zu rotieren.

Es ist besser, den öffentlichen Schlüsselsatz nicht nur einmal zu teilen, sondern eine Möglichkeit zu bieten, ihn automatisch zu aktualisieren. Andernfalls ist die Rotation zu einem neuen Schlüssel sehr schwierig. Dies geschieht häufig durch die Veröffentlichung des öffentlichen Keysets unter einer vertrauenswürdigen und sicheren URL. Ein Server, der Tokens verifiziert, muss den öffentlichen Schlüsselsatz regelmäßig, z. B. einmal täglich, von dieser URL abrufen. Um den Schlüssel zu rotieren, muss der neue öffentliche Schlüssel mindestens einen Tag vorher dem öffentlichen Schlüsselsatz hinzugefügt werden, bevor er zum Signieren von Tokens verwendet wird. Andernfalls werden die neuen Token, die mit dem neuen privaten Schlüssel signiert sind, von Servern abgelehnt, die noch den alten öffentlichen Schlüsselsatz verwenden.

JWT-MAC

Tink unterstützt auch JWT mit symmetrischen Schlüsseln mit dem Primitiv JwtMac. Verwenden Sie dieses Primitive nur, wenn die Tokens von derselben Einheit generiert und bestätigt werden. Die von diesem Primitive unterstützten Algorithmen sind HS256, HS384 und HS512.

Schlüsseltyp auswählen

JWT-MAC-Schlüsseltypen unterscheiden sich von normalen MAC-Schlüsseltypen. Wir empfehlen, JWT_HS256 für die meisten Anwendungsfälle zu verwenden.