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