ส่งออกเนื้อหาคีย์แบบเป็นโปรแกรม

Tink ไม่สนับสนุนแนวทางปฏิบัติที่ไม่ถูกต้องเกี่ยวกับคีย์ เช่น

  • สิทธิ์เข้าถึงเนื้อหาคีย์ลับของผู้ใช้ – ควรจัดเก็บคีย์ลับใน KMS แทนหากเป็นไปได้ โดยใช้วิธีที่ Tink รองรับระบบดังกล่าว
  • ผู้ใช้เข้าถึงบางส่วนของคีย์ ซึ่งมักทำให้เกิดข้อบกพร่องด้านความเข้ากันได้

แต่ในทางปฏิบัติ ก็มีบางกรณีที่จำเป็นต้องละเมิดหลักการเหล่านี้ Tink มีกลไกที่ทำให้สามารถทำเช่นนั้นได้อย่างปลอดภัยตามที่อธิบายไว้ในส่วนต่อไปนี้

โทเค็นการเข้าถึงคีย์ข้อมูลลับ

ผู้ใช้ต้องมีโทเค็น (ซึ่งมักจะเป็นออบเจ็กต์ของคลาสใดคลาสหนึ่งโดยไม่มีฟังก์ชันการทำงานใดๆ) จึงจะเข้าถึงข้อมูลคีย์ลับได้ โดยปกติแล้วโทเค็นจะมาจากวิธีการต่างๆ เช่น InsecureSecretKeyAccess.get() ภายใน Google ระบบจะป้องกันไม่ให้ผู้ใช้ใช้ฟังก์ชันนี้โดยใช้ระดับการเข้าถึง BUILD ของ Bazel ผู้ตรวจสอบความปลอดภัยภายนอก Google สามารถค้นหาฐานของโค้ดเพื่อดูการใช้งานฟังก์ชันนี้

ฟีเจอร์ที่มีประโยชน์อย่างหนึ่งของโทเค็นเหล่านี้คือการส่งต่อได้ ตัวอย่างเช่น สมมติว่าคุณมีฟังก์ชันที่จัดรูปแบบคีย์ Tink ที่กำหนดเองดังนี้

String serializeKey(Key key, @Nullable SecretKeyAccess secretKeyAccess);

สำหรับคีย์ที่มีเนื้อหาคีย์ลับ ฟังก์ชันนี้กำหนดให้ออบเจ็กต์ secretKeyAccess ต้องไม่เป็นค่าว่างและมีโทเค็น SecretKeyAccess จริงที่จัดเก็บไว้ ระบบจะละเว้น secretKeyAccess สำหรับคีย์ที่ไม่มีเนื้อหาลับ

เมื่อทราบฟังก์ชันดังกล่าวแล้ว คุณสามารถเขียนฟังก์ชันที่จัดรูปแบบชุดคีย์ทั้งหมดได้ดังนี้

String serializeKeyset(KeysetHandle keyset, @Nullable SecretKeyAccess
secretKeyAccess);

ฟังก์ชันนี้เรียกใช้ serializeKey สำหรับแต่ละคีย์ในชุดคีย์ภายใน และส่งผ่าน secretKeyAccess ที่ระบุไปยังฟังก์ชันที่สำคัญ ผู้ใช้ที่เรียกใช้ serializeKeyset โดยไม่ต้องเรียงลำดับเนื้อหาคีย์ลับจะใช้ null เป็นอาร์กิวเมนต์ที่ 2 ได้ ผู้ใช้ที่ต้องการทำให้เนื้อหาคีย์ลับเป็นอนุกรมควรใช้ InsecureSecretKeyAccess.get()

การเข้าถึงบางส่วนของคีย์

คีย์ Tink ไม่ได้มีแค่เนื้อหาคีย์ดิบ แต่ยังมีข้อมูลเมตาที่ระบุวิธีใช้คีย์ด้วย (ซึ่งไม่ควรใช้คีย์ในลักษณะอื่นใด) ตัวอย่างเช่น คีย์ RSA SSA PSS ใน Tink จะระบุว่าคีย์ RSA นี้ใช้ได้กับอัลกอริทึมลายเซ็น PSS โดยใช้ฟังก์ชันแฮชที่ระบุและความยาว Salt ที่ระบุเท่านั้น

บางครั้งคุณอาจต้องแปลงคีย์ Tink เป็นรูปแบบที่ต่างออกไป ซึ่งอาจไม่ได้ระบุข้อมูลเมตาทั้งหมดนี้อย่างชัดเจน ซึ่งโดยปกติหมายความว่าจะต้องระบุข้อมูลเมตาเมื่อใช้คีย์ กล่าวคือ สมมติว่ามีการใช้คีย์กับอัลกอริทึมเดียวกันเสมอ คีย์ดังกล่าวยังคงมีข้อมูลเมตาเดียวกันโดยปริยาย เพียงแต่จัดเก็บไว้ในตำแหน่งอื่นเท่านั้น

เมื่อแปลงคีย์ Tink ในรูปแบบอื่น คุณต้องตรวจสอบว่าข้อมูลเมตาของคีย์ Tink ตรงกับข้อมูลเมตา (ที่ระบุโดยนัย) ของรูปแบบคีย์อื่น หากไม่ตรงกัน Conversion จะดำเนินการไม่สำเร็จ

เนื่องจากการตรวจสอบเหล่านี้มักหายไปหรือไม่สมบูรณ์ Tink จึงจำกัดการเข้าถึง API ที่ให้สิทธิ์เข้าถึงเนื้อหาคีย์เพียงบางส่วน แต่อาจมีข้อผิดพลาดเป็นคีย์แบบเต็ม ใน Java นั้น Tink ใช้ RestrictedApi สำหรับการดำเนินการนี้ ส่วนใน C++ และ Golang จะใช้โทเค็นที่คล้ายกับโทเค็นการเข้าถึงคีย์ลับ

ผู้ใช้ API เหล่านี้มีหน้าที่รับผิดชอบในการป้องกันทั้งการโจมตีด้วยการใช้คีย์ซ้ำและการใช้งานร่วมกันไม่ได้

คุณมักจะพบวิธีการที่จำกัด "สิทธิ์เข้าถึงคีย์บางส่วน" ในบริบทของการส่งออกคีย์จากหรือนำเข้าคีย์ไปยัง Tink แนวทางปฏิบัติแนะนำต่อไปนี้อธิบายวิธีดำเนินงานอย่างปลอดภัยในสถานการณ์เหล่านี้

แนวทางปฏิบัติแนะนำ: ยืนยันพารามิเตอร์ทั้งหมดในการส่งออกคีย์

ตัวอย่างเช่น หากคุณเขียนฟังก์ชันที่ส่งออกคีย์สาธารณะ HPKE ให้ทำดังนี้

วิธีส่งออกคีย์สาธารณะที่ไม่ถูกต้อง

/** Provide the key to our users which don't have Tink. */
byte[] exportTinkHpkeKey(HpkePublicKey key) {
    return key.getPublicKeyBytes().toByteArray();
}

ซึ่งถือเป็นปัญหา หลังจากได้รับคีย์แล้ว บุคคลที่สามที่ใช้คีย์ดังกล่าวตั้งสมมติฐานเกี่ยวกับพารามิเตอร์ของคีย์ เช่น จะสันนิษฐานว่าอัลกอริทึม HPKE AEAD ที่ใช้สำหรับคีย์ 256 บิตนี้คือ AES-GCM

คําแนะนํา: ยืนยันว่าพารามิเตอร์เป็นค่าที่คุณต้องการในการส่งออกคีย์

วิธีส่งออกคีย์สาธารณะที่ดีกว่า

/** Provide the key to our users which don't have Tink. */
byte[] exportTinkHpkeKeyForOurUsers(HpkePublicKey key) {
    // Our users assume we use KEM_P256_HKDF_SHA256 for the KEM.
    if (!key.getParameters().getKemId().equals(HpkeParameters.KemId.KEM_P256_HKDF_SHA256)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    // Our users assume we use HKDF SHA256 to create the key material.
    if (!key.getParameters().getKdfId().equals(HpkeParameters.KdfId.HKDF_SHA256)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    // Our users assume that we use AES GCM with 256 bit keys.
    if (!key.getParameters().getAeadId().equals(HpkeParameters.AeadId.AES_256_GCM)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    // Our users assume we follow the standard and don't add a Tink style prefix
    if (!key.getParameters().getVariant().equals(HpkeParameters.Variant.NO_PREFIX)) {
        throw new IllegalArgumentException("Bad parameters");
    }
    return key.getPublicKeyBytes().toByteArray();
}

แนวทางปฏิบัติแนะนำ: ใช้ออบเจ็กต์ Tink โดยเร็วที่สุดในการนําเข้าคีย์

วิธีนี้ช่วยลดความเสี่ยงจากการโจมตีด้วยคีย์ที่คล้ายกัน เนื่องจากออบเจ็กต์คีย์ Tink จะระบุอัลกอริทึมที่ถูกต้องอย่างสมบูรณ์และจัดเก็บข้อมูลเมตาทั้งหมดไว้ด้วยกันพร้อมกับข้อมูลคีย์

ลองพิจารณาตัวอย่างต่อไปนี้

การใช้งานที่ไม่ได้พิมพ์:

void verifyEcdsaSignature(ECPoint ecPoint, byte[] signature, byte[] message)
        throws Exception {
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P256)
            .setHashType(EcdsaParameters.HashType.SHA256)
            .setVariant(EcdsaParameters.Variant.NO_PREFIX)
            .build();
    EcdsaPublicKey key =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(ecPoint)
            .build();
    KeysetHandle handle = KeysetHandle.newBuilder()
       .addEntry(KeysetHandle.importKey(key).withFixedId(1).makePrimary())
       .build();
    PublicKeyVerify publicKeyVerify = handle.getPrimitive(RegistryConfiguration.get(), PublicKeyVerify.class);
    publicKeyVerify.verify(signature, message);
}

วิธีนี้อาจทำให้เกิดข้อผิดพลาดได้ เนื่องจากคุณอาจลืมได้ง่ายว่าไม่ควรใช้ ecPoint เดียวกันกับอัลกอริทึมอื่น ตัวอย่างเช่น ถ้ามีฟังก์ชันที่คล้ายกันชื่อ encryptWithECHybridEncrypt อยู่ ผู้โทรอาจใช้จุดเส้นโค้งเดียวกันในการเข้ารหัสข้อความ ซึ่งอาจทำให้เกิดช่องโหว่ได้โดยง่าย

แต่จะดีกว่าหากเปลี่ยน verifyEcdsaSignature เพื่อให้อาร์กิวเมนต์แรกเป็น EcdsaPublicKey อันที่จริงแล้ว เมื่อใดก็ตามที่อ่านคีย์จากดิสก์หรือเครือข่าย ก็ควรแปลงเป็นออบเจ็กต์ EcdsaPublicKey ทันที เนื่องจากตอนนี้คุณทราบแล้วว่าจะใช้คีย์ในลักษณะใด จึงควรใช้รูปแบบนั้น

รหัสก่อนหน้ายังปรับปรุงให้ดียิ่งขึ้นได้ แทนที่จะส่งผ่านใน EcdsaPublicKey การส่งผ่านใน KeysetHandle จะดีกว่า ซึ่งจะเตรียมโค้ดสำหรับการหมุนเวียนคีย์โดยไม่ต้องดำเนินการใดๆ เพิ่มเติม คุณจึงควรใช้วิธีนี้

อย่างไรก็ตาม การปรับปรุงยังไม่เสร็จสิ้น: การใส่ออบเจ็กต์ PublicKeyVerify นั้นดีกว่ามาก เนื่องจากเพียงพอสำหรับฟังก์ชันนี้ การใส่ออบเจ็กต์ PublicKeyVerify จึงอาจเพิ่มตำแหน่งที่ใช้ฟังก์ชันนี้ได้ แต่ ณ จุดนี้ ฟังก์ชันกลับกลายเป็นเรื่องที่ไม่สำคัญ และอาจแทรกในบรรทัดได้

คำแนะนำ: เมื่ออ่านเนื้อหาคีย์จากดิสก์หรือเครือข่ายเป็นครั้งแรก ให้สร้างออบเจ็กต์ Tink ที่เกี่ยวข้องโดยเร็วที่สุด

การใช้งานที่พิมพ์:

KeysetHandle readEcdsaKeyFromFile(Path fileWithEcdsaKey) throws Exception {
    byte[] content = Files.readAllBytes(fileWithEcdsaKey);
    BigInteger x = new BigInteger(1, Arrays.copyOfRange(content, 0, 32));
    BigInteger y = new BigInteger(1, Arrays.copyOfRange(content, 32, 64));
    ECPoint point = new ECPoint(x, y);
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P256)
            .setHashType(EcdsaParameters.HashType.SHA256)
            .setVariant(EcdsaParameters.Variant.NO_PREFIX)
            .build();
    EcdsaPublicKey key =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(ecPoint)
            .build();
    return KeysetHandle.newBuilder()
       .addEntry(KeysetHandle.importKey(key).withFixedId(1).makePrimary())
       .build();
}

เมื่อใช้โค้ดดังกล่าว เราจะแปลงอาร์เรย์ไบต์เป็นออบเจ็กต์ Tink ทันทีที่อ่าน และระบุอัลกอริทึมที่ควรใช้อย่างครบถ้วน แนวทางนี้จะช่วยลดโอกาสที่จะถูกโจมตีด้วยคีย์ที่คล้ายกัน