데이터에 디지털 서명하려는 경우

대부분의 사용 사례에는 ECDSA_P256 키 유형이 있는 디지털 서명 원시를 사용하는 것이 좋습니다.

디지털 서명 원시 데이터는 아무도 데이터를 조작하지 않았음을 보장하고 데이터가 나로부터 온 것임을 증명합니다. 비대칭 방식으로, 비공개 키를 사용하여 데이터에 서명하고 공개 키를 사용하여 이를 확인합니다.

다음 예시를 통해 디지털 서명 원시 유형을 사용해 보세요.

// A utility for signing and verifying files using digital signatures.
#include <iostream>
#include <memory>
#include <ostream>
#include <string>

#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/keyset_handle.h"
#include "tink/public_key_sign.h"
#include "tink/public_key_verify.h"
#include "tink/signature/signature_config.h"
#include "tink/util/status.h"

ABSL_FLAG(std::string, keyset_filename, "", "Keyset file in JSON format");
ABSL_FLAG(std::string, mode, "", "Mode of operation (sign|verify)");
ABSL_FLAG(std::string, input_filename, "", "Filename to operate on");
ABSL_FLAG(std::string, signature_filename, "", "Path to the signature file");

namespace {

using ::crypto::tink::KeysetHandle;
using ::crypto::tink::PublicKeySign;
using ::crypto::tink::PublicKeyVerify;
using ::crypto::tink::util::Status;
using ::crypto::tink::util::StatusOr;

constexpr absl::string_view kSign = "sign";
constexpr absl::string_view kVerify = "verify";

void ValidateParams() {
  // ...
}

}  // namespace

namespace tink_cc_examples {

// Digital signature example CLI implementation.
Status DigitalSignatureCli(absl::string_view mode,
                           const std::string& keyset_filename,
                           const std::string& input_filename,
                           const std::string& signature_filename) {
  Status result = crypto::tink::SignatureConfig::Register();
  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();

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

  if (mode == kSign) {
    StatusOr<std::unique_ptr<PublicKeySign>> public_key_sign =
        (*keyset_handle)
            ->GetPrimitive<crypto::tink::PublicKeySign>(
                crypto::tink::ConfigGlobalRegistry());
    if (!public_key_sign.ok()) return public_key_sign.status();

    StatusOr<std::string> signature =
        (*public_key_sign)->Sign(*input_file_content);
    if (!signature.ok()) return signature.status();

    return WriteToFile(*signature, signature_filename);
  } else {  // mode == kVerify
    StatusOr<std::unique_ptr<PublicKeyVerify>> public_key_verify =
        (*keyset_handle)
            ->GetPrimitive<crypto::tink::PublicKeyVerify>(
                crypto::tink::ConfigGlobalRegistry());
    if (!public_key_verify.ok()) return public_key_verify.status();

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

    return (*public_key_verify)
        ->Verify(*signature_file_content, *input_file_content);
  }
}

}  // namespace tink_cc_examples

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

  ValidateParams();

  std::string mode = absl::GetFlag(FLAGS_mode);
  std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename);
  std::string input_filename = absl::GetFlag(FLAGS_input_filename);
  std::string signature_filename = absl::GetFlag(FLAGS_signature_filename);

  std::clog << "Using keyset in " << keyset_filename << " to " << mode;
  if (mode == kSign) {
    std::clog << " file " << input_filename
              << "; the resulting signature is written to "
              << signature_filename << '\n';
  } else {  // mode == kVerify
    std::clog << " the signature in " << signature_filename
              << " over the content of " << input_filename << '\n';
  }

  CHECK_OK(tink_cc_examples::DigitalSignatureCli(
      mode, keyset_filename, input_filename, signature_filename));
  return 0;
}
import (
	"bytes"
	"fmt"
	"log"

	"github.com/tink-crypto/tink-go/v2/insecurecleartextkeyset"
	"github.com/tink-crypto/tink-go/v2/keyset"
	"github.com/tink-crypto/tink-go/v2/signature"
)

func Example() {
	// A private keyset created with
	// "tinkey create-keyset --key-template=ECDSA_P256 --out private_keyset.cfg".
	// Note that this keyset has the secret key information in cleartext.
	privateJSONKeyset := `{
		"key": [{
			"keyData": {
					"keyMaterialType":
							"ASYMMETRIC_PRIVATE",
					"typeUrl":
							"type.googleapis.com/google.crypto.tink.EcdsaPrivateKey",
					"value":
							"EkwSBggDEAIYAhogEiSZ9u2nDtvZuDgWgGsVTIZ5/V08N4ycUspTX0RYRrkiIHpEwHxQd1bImkyMvV2bqtUbgMh5uPSTdnUEGrPXdt56GiEA3iUi+CRN71qy0fOCK66xAW/IvFyjOGtxjppRhSFUneo="
			},
			"keyId": 611814836,
			"outputPrefixType": "TINK",
			"status": "ENABLED"
		}],
		"primaryKeyId": 611814836
	}`

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

	// 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 Signer primitive from privateKeysetHandle.
	signer, err := signature.NewSigner(privateKeysetHandle)
	if err != nil {
		log.Fatal(err)
	}

	// Use the primitive to sign a message. In this case, the primary key of the
	// keyset will be used (which is also the only key in this example).
	data := []byte("data")
	sig, err := signer.Sign(data)
	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 := signature.NewVerifier(publicKeysetHandle)
	if err != nil {
		log.Fatal(err)
	}

	if err = verifier.Verify(sig, data); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("sig is valid")
	// Output: sig is valid
}
package signature;

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.PublicKeySign;
import com.google.crypto.tink.PublicKeyVerify;
import com.google.crypto.tink.RegistryConfiguration;
import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
import com.google.crypto.tink.signature.SignatureConfig;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * A command-line utility for digitally signing and verifying a file.
 *
 * <p>It loads cleartext keys from disk - this is not recommended!
 *
 * <p>It requires the following arguments:
 *
 * <ul>
 *   <li>mode: either 'sign' or 'verify'.
 *   <li>key-file: Read the key material from this file.
 *   <li>input-file: Read the input from this file.
 *   <li>signature-file: name of the file containing a hexadecimal signature of the input file.
 */
public final class SignatureExample {
  public static void main(String[] args) throws Exception {
    if (args.length != 4) {
      System.err.printf("Expected 4 parameters, got %d\n", args.length);
      System.err.println(
          "Usage: java SignatureExample sign/verify key-file input-file signature-file");
      System.exit(1);
    }

    String mode = args[0];
    if (!mode.equals("sign") && !mode.equals("verify")) {
      System.err.println("Incorrect mode. Please select sign or verify.");
      System.exit(1);
    }
    Path keyFile = Paths.get(args[1]);
    byte[] msg = Files.readAllBytes(Paths.get(args[2]));
    Path signatureFile = Paths.get(args[3]);

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

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

    if (mode.equals("sign")) {
      // Get the primitive.
      PublicKeySign signer = handle.getPrimitive(RegistryConfiguration.get(), PublicKeySign.class);

      // Use the primitive to sign data.
      byte[] signature = signer.sign(msg);
      Files.write(signatureFile, signature);
    } else {
      byte[] signature = Files.readAllBytes(signatureFile);

      // Get the primitive.
      PublicKeyVerify verifier =
          handle.getPrimitive(RegistryConfiguration.get(), PublicKeyVerify.class);

      verifier.verify(signature, msg);
    }
  }

  private SignatureExample() {}
}

방법 안내

import tink
from tink import secret_key_access
from tink import signature


def example():
  """Sign and verify using digital signatures."""
  # Register the signature key managers. This is needed to create
  # PublicKeySign and PublicKeyVerify primitives later.
  signature.register()

  # A private keyset created with
  # "tinkey create-keyset --key-template=ECDSA_P256 --out private_keyset.cfg".
  # Note that this keyset has the secret key information in cleartext.
  private_keyset = r"""{
      "key": [{
          "keyData": {
              "keyMaterialType":
                  "ASYMMETRIC_PRIVATE",
              "typeUrl":
                  "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey",
              "value":
                  "EkwSBggDEAIYAhogEiSZ9u2nDtvZuDgWgGsVTIZ5/V08N4ycUspTX0RYRrkiIHpEwHxQd1bImkyMvV2bqtUbgMh5uPSTdnUEGrPXdt56GiEA3iUi+CRN71qy0fOCK66xAW/IvFyjOGtxjppRhSFUneo="
          },
          "keyId": 611814836,
          "outputPrefixType": "TINK",
          "status": "ENABLED"
      }],
      "primaryKeyId": 611814836
  }"""

  # The corresponding public keyset created with
  # "tinkey create-public-keyset --in private_keyset.cfg"
  public_keyset = r"""{
      "key": [{
          "keyData": {
              "keyMaterialType":
                  "ASYMMETRIC_PUBLIC",
              "typeUrl":
                  "type.googleapis.com/google.crypto.tink.EcdsaPublicKey",
              "value":
                  "EgYIAxACGAIaIBIkmfbtpw7b2bg4FoBrFUyGef1dPDeMnFLKU19EWEa5IiB6RMB8UHdWyJpMjL1dm6rVG4DIebj0k3Z1BBqz13beeg=="
          },
          "keyId": 611814836,
          "outputPrefixType": "TINK",
          "status": "ENABLED"
      }],
      "primaryKeyId": 611814836
  }"""

  # Create a keyset handle from the cleartext keyset in the previous
  # step. The keyset handle provides abstract access to the underlying keyset to
  # limit the exposure of accessing the raw key material. WARNING: In practice,
  # it is unlikely you will want to use tink.json_proto_keyset_format.parse, as
  # it implies that your key material is passed in cleartext which is a security
  # risk.
  private_keyset_handle = tink.json_proto_keyset_format.parse(
      private_keyset, secret_key_access.TOKEN
  )

  # Retrieve the PublicKeySign primitive we want to use from the keyset
  # handle.
  sign_primitive = private_keyset_handle.primitive(signature.PublicKeySign)

  # Use the primitive to sign a message. In this case the primary key of the
  # keyset will be used (which is also the only key in this example).
  sig = sign_primitive.sign(b'msg')

  # Create a keyset handle from the keyset containing the public key. Because
  # this keyset does not contain any secrets, we can use
  # `parse_without_secret`.
  public_keyset_handle = tink.json_proto_keyset_format.parse_without_secret(
      public_keyset
  )

  # Retrieve the PublicKeyVerify primitive we want to use from the keyset
  # handle.
  verify_primitive = public_keyset_handle.primitive(signature.PublicKeyVerify)

  # Use the primitive to verify that `sig` is valid signature for the message.
  # Verify finds the correct key in the keyset. If no key is found or
  # verification fails, it raises an error.
  verify_primitive.verify(sig, b'msg')

  # Note that we can also get the public keyset handle from the private keyset
  # handle. The verification works the same as above.
  public_keyset_handle2 = private_keyset_handle.public_keyset_handle()
  verify_primitive2 = public_keyset_handle2.primitive(signature.PublicKeyVerify)
  verify_primitive2.verify(sig, b'msg')

디지털 서명

디지털 서명 원시 함수를 사용하면 아무도 데이터를 조작하지 않았는지 확인할 수 있습니다. 서명된 데이터의 신뢰성과 무결성은 보장하지만 보안 비밀성은 보장하지 않습니다. 비대칭이므로 키 쌍 (공개 키 및 비공개 키)을 사용합니다.

디지털 서명 원시에는 다음과 같은 속성이 있습니다.

  • 진위성: 비공개 키가 없으면 PublicKeyVerify.Verify(signature, message)가 유효성을 검사하는 서명을 만들 수 없습니다.
  • 비대칭: 서명을 만들 때 서명을 확인할 때와 다른 키가 사용됩니다. 이렇게 하면 서명을 직접 만들 수 없는 당사자에게 서명을 확인할 공개 키를 배포할 수 있습니다.

비대칭이 필요하지 않은 경우 더 간단하고 효율적인 MAC 원시를 대신 사용하는 것이 좋습니다.

디지털 서명의 기능은 Tink에서 다음과 같은 프리미티브 쌍으로 표현됩니다.

  • 데이터 서명용 PublicKeySign
  • 서명 확인을 위한 PublicKeyVerify

키 유형 선택

대부분의 사용 사례에는 ECDSA_P256을 사용하는 것이 좋지만 다양한 옵션이 있습니다. 일반적으로 다음이 적용됩니다.

  • ECDSA_P256은 가장 널리 사용되는 옵션이며 적절한 기본값입니다. 하지만 ECDSA 서명은 변형될 수 있습니다.
  • ED25519는 결정론적 서명을 생성하고 ECDSA_P256보다 우수한 성능을 제공합니다.
  • RSA_SSA_PKCS1_3072_SHA256_F4는 결정적 서명을 생성하고 최상의 확인 성능을 제공합니다 (단, 서명 속도는 ECDSA_P256 또는 ED25519보다 훨씬 느림).

최소 보안 보장

  • 서명할 데이터의 길이는 임의일 수 있습니다.
  • 타원 곡선 기반 스키마의 적응형 선택 메시지 공격에 대한 128비트 보안 수준
  • RSA 기반 스킴의 적응형 선택 메시지 공격에 대한 112비트 보안 수준 (2,048비트 키 허용)

가공성

공격자가 이미 서명된 메시지에 대해 다른 유효한 서명을 만들 수 있는 경우 서명 스키마는 조작 가능합니다. 이는 대부분의 시나리오에서는 문제가 되지 않지만, 프로그래머가 유효한 서명이 고유하다고 암시적으로 가정하는 경우도 있으며, 이로 인해 예상치 못한 결과가 발생할 수 있습니다.