วิธีเข้ารหัส String ใน Java


150

สิ่งที่ฉันต้องการคือการเข้ารหัสสตริงที่จะแสดงในบาร์โค้ด 2D (PDF-417) ดังนั้นเมื่อใครบางคนได้รับแนวคิดในการสแกนมันจะไม่สามารถอ่านได้เลย

ข้อกำหนดอื่น ๆ :

  • ไม่ควรซับซ้อน
  • ไม่ควรประกอบด้วย RSA, โครงสร้างพื้นฐาน PKI, คู่กุญแจ ฯลฯ

มันจะง่ายพอที่จะกำจัดผู้คนที่สอดแนมและถอดรหัสได้ง่ายสำหรับ บริษัท อื่น ๆ ที่สนใจจะรับข้อมูลนั้น พวกเขาโทรหาเราเราบอกพวกเขาถึงมาตรฐานหรือให้กุญแจง่ายๆที่พวกเขาสามารถนำไปใช้ในการถอดรหัสได้

อาจเป็นไปได้ว่า บริษัท เหล่านั้นสามารถใช้เทคโนโลยีที่แตกต่างกันดังนั้นจึงเป็นการดีที่จะยึดมั่นในมาตรฐานซึ่งไม่ได้ผูกติดอยู่กับแพลตฟอร์มหรือเทคโนโลยีพิเศษบางอย่าง

คุณแนะนำอะไร? มีบางคลาส Java ที่ทำencrypt()& decrypt()ไม่มีความยุ่งยากในการบรรลุมาตรฐานความปลอดภัยสูงหรือไม่



การเตือน คำตอบมากมายด้านล่างแสดงวิธีหนึ่งหรือวิธีอื่นในการเข้ารหัสชนิดใด ๆบน Java รู้รอบอาจไม่สะท้อนการเข้ารหัสลับที่ดีและอาจไม่ได้รับการตรวจสอบอย่างดี ไม่มีสิ่งเช่นคัดลอก / วางระบบรักษาความปลอดภัย คำตอบควรคำนึงถึงการแปลงสตริงเป็นอย่างน้อย คำถามจริงที่มีบาร์โค้ด 2 มิติรวมอยู่นั้นกว้างเกินไปและควรต้องการโซลูชันเฉพาะสำหรับลูกค้า
Maarten Bodewes

คำตอบ:


156

นี่คือหน้าแรกที่ปรากฏขึ้นผ่าน Google และช่องโหว่ด้านความปลอดภัยในการใช้งานทั้งหมดทำให้ฉันประจบประแจงดังนั้นฉันจึงโพสต์สิ่งนี้เพื่อเพิ่มข้อมูลเกี่ยวกับการเข้ารหัสสำหรับผู้อื่นเนื่องจาก7 ปีนับจากโพสต์ต้นฉบับ ฉันสำเร็จการศึกษาระดับปริญญาโทด้านวิศวกรรมคอมพิวเตอร์และใช้เวลาศึกษาและเรียนรู้การเข้ารหัสมากมายดังนั้นฉันจึงโยนสองเซ็นต์ของฉันเพื่อทำให้อินเทอร์เน็ตปลอดภัยยิ่งขึ้น

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

อัปเดต 4/5/18:ฉันเขียนใหม่บางส่วนเพื่อให้ง่ายต่อการเข้าใจและเปลี่ยนไลบรารี่ที่แนะนำจากJasyptไปเป็น Tink ไลบรารี่ใหม่ของ Googleฉันขอแนะนำให้ลบJasyptออกจากการตั้งค่าที่มีอยู่ทั้งหมด

คำนำ

ฉันจะร่างพื้นฐานของการเข้ารหัสแบบสมมาตรที่ปลอดภัยด้านล่างและชี้ให้เห็นข้อผิดพลาดทั่วไปที่ฉันเห็นทางออนไลน์เมื่อผู้คนติดตั้ง crypto ด้วยตนเองด้วยไลบรารี Java มาตรฐาน หากคุณต้องการข้ามรายละเอียดทั้งหมดไปที่ห้องสมุดใหม่ของ Google Tinkนำเข้าสู่โครงการของคุณและใช้โหมด AES-GCM สำหรับการเข้ารหัสทั้งหมดของคุณและคุณจะปลอดภัย

ตอนนี้ถ้าคุณต้องการเรียนรู้รายละเอียด nitty gritty เกี่ยวกับวิธีการเข้ารหัสใน java อ่าน :)

บล็อกยันต์

สิ่งแรกอันดับแรกคุณต้องเลือก Block Cipher key symmetric Block Cipher เป็นฟังก์ชัน / โปรแกรมคอมพิวเตอร์ที่ใช้สร้าง Pseudo-Randomness Pseudo-Randomness เป็นการสุ่มแบบปลอมที่ไม่มีคอมพิวเตอร์เครื่องอื่นนอกเหนือจาก Quantum Computer จะสามารถบอกความแตกต่างระหว่างมันและการสุ่มที่แท้จริงได้ Block Cipher นั้นเหมือนกับ Building Block ถึงการเข้ารหัสและเมื่อใช้กับโหมดหรือแบบแผนต่าง ๆ เราสามารถสร้างการเข้ารหัสได้

ตอนนี้เกี่ยวกับบล็อก Cipher อัลกอริทึมที่มีอยู่ในวันนี้ให้แน่ใจว่าจะไม่ฉันทำซ้ำไม่เคยใช้DESผมแม้จะพูดไม่เคยใช้3DES เพียงตัวเลขบล็อกว่าแม้ Snowden ปล่อยเอ็นเอสเอก็สามารถที่จะตรวจสอบความเป็นจริงใกล้เคียงกับการสุ่มหลอกที่เป็นไปได้คือการเข้ารหัส AES 256 นอกจากนี้ยังมี AES 128; ความแตกต่างคือ AES 256 ทำงานในบล็อก 256 บิตในขณะที่ AES 128 ทำงานใน 128 บล็อก สรุปแล้ว AES 128 นั้นถือว่าปลอดภัยแม้ว่าจะมีการค้นพบจุดอ่อน แต่ 256 นั้นแข็งแกร่งพอ ๆ กับที่ได้รับ

ความจริงเรื่องความสนุกDESถูกทำลายโดย NSA เมื่อมันถูกก่อตั้งขึ้นในตอนแรกและจริง ๆ แล้วเก็บเป็นความลับไม่กี่ปี ถึงแม้ว่าบางคนยังคงเรียกร้อง3DESมีความปลอดภัยมีค่อนข้างไม่กี่เอกสารงานวิจัยที่ได้พบและวิเคราะห์จุดอ่อนใน3DES

โหมดการเข้ารหัส

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

นี่คือตัวอย่างของโหมดการเข้ารหัสและโหมดที่ง่ายที่สุดที่รู้จักกันในนาม ECB เพื่อให้คุณสามารถเข้าใจด้วยตาว่าเกิดอะไรขึ้น:

โหมด ECB

โหมดการเข้ารหัสที่คุณจะเห็นได้ทั่วไปทางออนไลน์ ได้แก่

ECB CTR, CBC, GCM

มีโหมดอื่น ๆ นอกเหนือจากที่อยู่ในรายการและนักวิจัยมักจะทำงานในโหมดใหม่เพื่อปรับปรุงปัญหาที่มีอยู่

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

เมื่อใช้งานใน Java โปรดทราบว่าหากคุณใช้รหัสต่อไปนี้โหมด ECB จะถูกตั้งค่าตามค่าเริ่มต้น:

Cipher cipher = Cipher.getInstance("AES");

... อันตรายนี่คือความเลวทราม! และน่าเสียดายที่สิ่งนี้มีอยู่ทั่ว StackOverflow และออนไลน์ในบทแนะนำและตัวอย่าง

Nonces และ IV

ในการตอบสนองต่อปัญหาที่พบในคำนามโหมด ECB หรือที่เรียกว่า IV ถูกสร้างขึ้น แนวคิดคือเราสร้างตัวแปรสุ่มใหม่และแนบกับการเข้ารหัสทุกครั้งดังนั้นเมื่อคุณเข้ารหัสข้อความสองข้อความที่เหมือนกันพวกเขาจะแตกต่างกันออกไป ความงามที่อยู่เบื้องหลังนี้คือ IV หรือ nonce เป็นความรู้สาธารณะ นั่นหมายความว่าผู้โจมตีสามารถเข้าถึงสิ่งนี้ได้ แต่ตราบใดที่พวกเขาไม่มีกุญแจของคุณพวกเขาไม่สามารถทำอะไรกับความรู้นั้นได้

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

การสร้างแบบสุ่ม IV

SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);

หมายเหตุ: SHA1 นั้นใช้งานไม่ได้ แต่ฉันไม่สามารถหาวิธีนำ SHA256 ไปใช้ในกรณีการใช้งานได้อย่างถูกต้องดังนั้นหากใครต้องการที่จะถอดรหัสและอัพเดตมันจะยอดเยี่ยมมาก! การโจมตีของ SHA1 นั้นยังคงไม่เป็นไปตามปกติเนื่องจากอาจใช้เวลาสองสามปีในการโจมตีแบบกลุ่มใหญ่ ตรวจสอบรายละเอียดที่นี่

การใช้ CTR

ไม่จำเป็นต้องใช้ช่องว่างภายในสำหรับโหมด CTR

 Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

การนำ CBC ไปใช้

หากคุณเลือกที่จะใช้โหมด CBC ทำได้ด้วย PKCS7Padding ดังนี้:

 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

ช่องโหว่ CBC และ CTR และทำไมคุณควรใช้ GCM

แม้ว่าโหมดอื่น ๆ เช่น CBC และ CTR จะปลอดภัย แต่พวกเขาก็พบเจอปัญหาที่ผู้โจมตีสามารถพลิกข้อมูลที่เข้ารหัสได้เปลี่ยนค่าเมื่อถอดรหัส สมมติว่าคุณเข้ารหัสข้อความธนาคารในจินตนาการ "ขาย 100" ข้อความที่เข้ารหัสของคุณจะมีลักษณะเช่นนี้ "eu23ng" ผู้โจมตีเปลี่ยนหนึ่งบิตเป็น "eu53ng" และในทันทีเมื่อถอดรหัสข้อความของคุณมันจะอ่านว่า "ขาย 900"

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

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

ตอนนี้ถ้าคุณไม่ต้องการที่จะใช้ GCM นี่คือการเชื่อมโยงไปยังการดำเนินงานที่ดี GCM อย่างไรก็ตามฉันไม่สามารถรับรองความปลอดภัยหรือหากมีการใช้งานอย่างเหมาะสม แต่จะทำให้พื้นฐานลดลง โปรดทราบด้วย GCM ว่าไม่มีช่องว่างภายใน

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

คีย์เทียบกับรหัสผ่าน

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

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

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

นักพัฒนา Android

จุดสำคัญอย่างหนึ่งที่ชี้ให้เห็นที่นี่คือรู้ว่ารหัส Android ของคุณสามารถย้อนกลับได้และส่วนใหญ่โค้ด Java ก็เช่นกัน นั่นหมายความว่าถ้าคุณเก็บรหัสผ่านเป็นข้อความธรรมดาในรหัสของคุณ แฮกเกอร์สามารถเรียกดูได้อย่างง่ายดาย โดยปกติสำหรับการเข้ารหัสประเภทนี้คุณต้องการใช้ Asymmetric Cryptography และอื่น ๆ นี่อยู่นอกขอบเขตของโพสต์นี้ดังนั้นฉันจะหลีกเลี่ยงการลงไป

การอ่านที่น่าสนใจจากปี 2013 : ชี้ให้เห็นว่า 88% ของการใช้ Crypto ใน Android นั้นทำได้ไม่ถูกต้อง

ความคิดสุดท้าย

อีกครั้งฉันขอแนะนำให้หลีกเลี่ยงการใช้ไลบรารี java สำหรับ crypto โดยตรงและใช้Google Tinkมันจะช่วยให้คุณปวดหัวเพราะพวกเขาทำงานได้ดีมากในการใช้อัลกอริธึมทั้งหมดอย่างถูกต้อง และจากนั้นตรวจสอบให้แน่ใจว่าคุณตรวจสอบปัญหาที่เกิดขึ้นใน Tink GitHub ช่องโหว่จะปรากฏขึ้นที่นี่และที่นั่น

หากคุณมีคำถามหรือข้อเสนอแนะอย่าลังเลที่จะแสดงความคิดเห็น! การรักษาความปลอดภัยมีการเปลี่ยนแปลงอยู่ตลอดเวลาและคุณต้องทำให้ดีที่สุดเพื่อให้ทัน :)


15
นี่คือสิ่งที่สะอาดที่สุดที่ฉันเคยเห็น
Seraf

1
@ SabirKhan มันอาจเป็นสาเหตุของความกังวล แต่อัลกอริธึมหลักยังไม่พังดังนั้นฉันจะไม่กังวลเกินไป ในกรณีที่คุณไม่ไว้ใจมันก็ลองดูที่github.com/google/keyczarมันได้รับการพัฒนาโดยทีมรักษาความปลอดภัยของ Google
Konstantino Sparakis

1
@ KonstantinoSparakis: ถ้าฉันไม่ได้ตีความเอกสารของ Jasypt BasicTextEncryptor และ StrongTextEncryptor ของ Jasypt คลาสเหล่านั้นใช้DESและ3DESสำหรับการเข้ารหัสซึ่งเป็นสิ่งที่คุณบอกผู้อ่านว่าไม่ควรใช้ IMO คุณควรแทนที่ตัวอย่างโค้ดที่กำหนดด้วยอันที่ใช้ StandardPBEStringEncryptor ของ Jasypt และกำหนดอัลกอริทึม AES ด้วยตนเอง
xpages-noob

1
@ xpages-noob ฉันได้อัปเดตโพสต์แล้ว ฉันพบ Google Tink จริง ๆ แล้วซึ่งเป็นไลบรารีที่สนับสนุนใหม่ล่าสุดสำหรับ crypto ดังนั้นคุณควรลองดู!
Konstantino Sparakis

2
ขนาดบล็อก AES คือ 128 บิต ใน AES 256 ขนาดของคีย์คือ 256 บิต ในทำนองเดียวกัน AES 192 และ AES 128 นอกจากนี้ตั้งแต่ Java 8, getInstanceStrong()วิธีการที่Cipherเป็นที่นิยมมากกว่า SHA1PRNG
Saptarshi Basu

110

ผมอยากแนะนำให้ใช้บางหัดสมมาตรมาตรฐานที่สามารถใช้ได้อย่างกว้างขวางเช่นDES , 3DESหรือAES แม้ว่านั่นจะไม่ใช่อัลกอริทึมที่ปลอดภัยที่สุด แต่ก็มีการใช้งานมากมายและคุณเพียงแค่ต้องมอบกุญแจให้กับทุกคนที่ควรจะถอดรหัสข้อมูลในบาร์โค้ด javax.crypto.Cipherคือสิ่งที่คุณต้องการทำงานกับที่นี่

สมมติว่าไบต์ที่จะเข้ารหัสนั้นมีอยู่

byte[] input;

ถัดไปคุณจะต้องใช้คีย์และการเริ่มต้นเวกเตอร์ไบต์

byte[] keyBytes;
byte[] ivBytes;

ตอนนี้คุณสามารถเริ่มต้นการเข้ารหัสสำหรับอัลกอริทึมที่คุณเลือก:

// wrap key data in Key/IV specs to pass to cipher
SecretKeySpec key = new SecretKeySpec(keyBytes, "DES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// create the cipher with the algorithm you choose
// see javadoc for Cipher class for more info, e.g.
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");

การเข้ารหัสจะเป็นดังนี้:

cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted= new byte[cipher.getOutputSize(input.length)];
int enc_len = cipher.update(input, 0, input.length, encrypted, 0);
enc_len += cipher.doFinal(encrypted, enc_len);

และถอดรหัสเช่นนี้

cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = new byte[cipher.getOutputSize(enc_len)];
int dec_len = cipher.update(encrypted, 0, enc_len, decrypted, 0);
dec_len += cipher.doFinal(decrypted, dec_len);

9
ฉันขอแนะนำให้คุณอัปเดตตัวอย่างนี้เพื่ออ้างอิงDESedeอัลกอริทึมได้หรือไม่ เนื่องจากนี่เป็นคำถามยอดนิยม (และคำตอบ) มันน่าละอายที่จะกระตุ้นให้คนใช้ DES เนื่องจากตัวเลขนั้นอ่อนแอตามมาตรฐานของทุกวันนี้
Duncan Jones

มีบางอย่างผิดปกติกับ javax.crypto.BadPaddingException: การบล็อกขั้นสุดท้ายไม่ได้ถูกเพิ่มอย่างเหมาะสมในขณะที่ถอดรหัส
อยากรู้อยากเห็น

2
@Duncan อันที่จริง DES อ่อนแอ แต่ฉันคิดว่า AES น่าจะดีกว่า DESede (aka TipleDES): http://security.stackexchange.com/a/26181/69785
Piovezan

2
สิ่งนี้ควรได้รับการปรับปรุงให้มี AES / GCM / NoPadding, DES มีความเสี่ยงต่อการโจมตีแบบ bruteforce ไม่แนะนำ
TripleDes

1
คำตอบจาก Konstantino Sparakis ด้านล่างดีกว่านี้มาก
Steve

22

คำเตือน

อย่าใช้สิ่งนี้เป็นมาตรการรักษาความปลอดภัยบางประเภท

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

ชี้โดย Moussa


ฉันใช้ Base64Encoder / Decoder ของซันซึ่งพบได้ใน JRE ของซันเพื่อหลีกเลี่ยง JAR อื่นใน lib นั่นเป็นอันตรายจากการใช้ OpenJDK หรือ JRE ของคนอื่น นอกจากนั้นมีเหตุผลอื่นอีกไหมที่ฉันควรพิจารณาใช้ Apache Commons lib กับ Encoder / Decoder

public class EncryptUtils {
    public static final String DEFAULT_ENCODING = "UTF-8"; 
    static BASE64Encoder enc = new BASE64Encoder();
    static BASE64Decoder dec = new BASE64Decoder();

    public static String base64encode(String text) {
        try {
            return enc.encode(text.getBytes(DEFAULT_ENCODING));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }//base64encode

    public static String base64decode(String text) {
        try {
            return new String(dec.decodeBuffer(text), DEFAULT_ENCODING);
        } catch (IOException e) {
            return null;
        }
    }//base64decode

    public static void main(String[] args) {
        String txt = "some text to be encrypted";
        String key = "key phrase used for XOR-ing";
        System.out.println(txt + " XOR-ed to: " + (txt = xorMessage(txt, key)));

        String encoded = base64encode(txt);       
        System.out.println(" is encoded to: " + encoded + " and that is decoding to: " + (txt = base64decode(encoded)));
        System.out.print("XOR-ing back to original: " + xorMessage(txt, key));
    }

    public static String xorMessage(String message, String key) {
        try {
            if (message == null || key == null) return null;

            char[] keys = key.toCharArray();
            char[] mesg = message.toCharArray();

            int ml = mesg.length;
            int kl = keys.length;
            char[] newmsg = new char[ml];

            for (int i = 0; i < ml; i++) {
                newmsg[i] = (char)(mesg[i] ^ keys[i % kl]);
            }//for i

            return new String(newmsg);
        } catch (Exception e) {
            return null;
        }
    }//xorMessage
}//class

1
ฉันยังใช้ข้อเสนอการแก้ปัญหานี้ผ่านทาง sun.misc.BASE64Encoder แต่เมื่อใช้สตริงที่ค่อนข้างใหญ่ในการเข้ารหัสเอ็นโค้ดเดอร์ส่งคืนสตริงที่มีขนาดใหญ่ (แต่ละตัวอักษร 76 ตัว) จากนั้นฉันเปลี่ยนเป็น Apache Commons Codec Base64 ซึ่งเสนอวิธีการเข้ารหัสที่ไม่ทำให้อ้วน!
basZero

78
กลไกการเข้ารหัสที่คุณอธิบายไว้เป็นอันตรายมากหากใช้มากกว่าหนึ่งครั้ง นั่นคือเหตุผลที่เรียกว่าแผ่น One-time ผู้โจมตีสามารถกู้คืนรหัสลับได้อย่างง่ายดายโดยใช้ข้อความเข้ารหัส 2 ข้อความ xor 2 ข้อความเข้ารหัสและคุณจะได้รับกุญแจ ง่าย ๆ !
xtrem

3
ความคิดของมันไม่ได้เป็นเรื่องหนักหน่วงเพียงเพื่อจะสะท้อนผู้คนจากการพยายามอ่านสิ่งที่เขียนในบาร์โค้ด 2D-PDF 417 และมีเพียงดัชนีบางตัวที่ไม่สำคัญสำหรับทุกคน ...
ante.sabo

2
ตกลง. แค่กังวลว่ามีคนใช้สิ่งนี้เป็นกลไกการเข้ารหัส
xtrem

สำหรับการเข้ารหัสสามารถหลีกเลี่ยงการเข้ารหัสได้เช่นพื้นฐาน BASE64Encoder
Jagrut Dalwadi

13

ขอบคุณ ive ทำให้คลาสนี้ใช้รหัสของคุณอาจมีคนพบว่ามัน userfull

วัตถุ

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class ObjectCrypter {

private Cipher deCipher;
private Cipher enCipher;
private SecretKeySpec key;
private IvParameterSpec ivSpec;


public ObjectCrypter(byte[] keyBytes,   byte[] ivBytes) {
    // wrap key data in Key/IV specs to pass to cipher


     ivSpec = new IvParameterSpec(ivBytes);
    // create the cipher with the algorithm you choose
    // see javadoc for Cipher class for more info, e.g.
    try {
         DESKeySpec dkey = new  DESKeySpec(keyBytes);
          key = new SecretKeySpec(dkey.getKey(), "DES");
         deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
         enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
public byte[] encrypt(Object obj) throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, IllegalBlockSizeException, ShortBufferException, BadPaddingException {
    byte[] input = convertToByteArray(obj);
    enCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

    return enCipher.doFinal(input);




//  cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
//  byte[] encypted = new byte[cipher.getOutputSize(input.length)];
//  int enc_len = cipher.update(input, 0, input.length, encypted, 0);
//  enc_len += cipher.doFinal(encypted, enc_len);
//  return encypted;


}
public Object decrypt( byte[]  encrypted) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException, ClassNotFoundException {
    deCipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

    return convertFromByteArray(deCipher.doFinal(encrypted));

}



private Object convertFromByteArray(byte[] byteObject) throws IOException,
        ClassNotFoundException {
    ByteArrayInputStream bais;

    ObjectInputStream in;
    bais = new ByteArrayInputStream(byteObject);
    in = new ObjectInputStream(bais);
    Object o = in.readObject();
    in.close();
    return o;

}



private byte[] convertToByteArray(Object complexObject) throws IOException {
    ByteArrayOutputStream baos;

    ObjectOutputStream out;

    baos = new ByteArrayOutputStream();

    out = new ObjectOutputStream(baos);

    out.writeObject(complexObject);

    out.close();

    return baos.toByteArray();

}


}

โพสต์คำถามที่เกี่ยวข้องที่นี่ !
user2023507

มันไม่ควรเป็นกรณีที่ผ่านคีย์ที่แตกต่างกันระหว่างการเข้ารหัสและถอดรหัสไม่ควรกลับข้อความกลับมา? ดูเหมือนจะไม่เกิดขึ้นที่นี่ PS: ฉันใช้วัตถุต่าง ๆ ของคลาสนี้เพื่อทำการทดสอบนี้
instanceOfObject

6

อัพเดทเมื่อวันที่ 12-DEC-2019

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

การใช้ลำดับหรือการประทับเวลาเป็น IV ก็เป็นไปได้เช่นกัน แต่มันอาจไม่น่าสนใจเท่าที่ควร ตัวอย่างเช่นหากระบบไม่ถูกต้องติดตามลำดับที่ใช้แล้วเป็น IV ในร้านค้าแบบถาวรการภาวนาอาจทำซ้ำ IV หลังจากรีบูตระบบ ในทำนองเดียวกันไม่มีนาฬิกาที่สมบูรณ์แบบ ปรับนาฬิกาคอมพิวเตอร์ ฯลฯ

นอกจากนี้ควรหมุนคีย์หลังจากการเรียกใช้ทุก 2 ^ 32 สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับความต้องการ IV ที่อ้างถึงนี้คำตอบและคำแนะนำ NIST


นี่คือรหัสการเข้ารหัสและถอดรหัสที่ฉันเพิ่งเขียนใน Java 8 โดยพิจารณาประเด็นต่อไปนี้ หวังว่าบางคนจะพบว่ามีประโยชน์นี้:

  1. อัลกอริทึมการเข้ารหัส : บล็อกเข้ารหัส AES ด้วยคีย์ 256 บิตถือว่าปลอดภัยเพียงพอ ในการเข้ารหัสข้อความที่สมบูรณ์จำเป็นต้องเลือกโหมด แนะนำให้ใช้การเข้ารหัสที่รับรองความถูกต้อง (ซึ่งมีทั้งการรักษาความลับและความสมบูรณ์) GCM, CCM และ EAX เป็นโหมดเข้ารหัสที่ใช้บ่อยที่สุด โดยทั่วไปแล้วจะต้องการ GCM และทำงานได้ดีในสถาปัตยกรรม Intel ซึ่งมีคำแนะนำเฉพาะสำหรับ GCM ทั้งสามโหมดนี้เป็นโหมดที่อิงกับ CTR (อิงตามเคาน์เตอร์) ดังนั้นจึงไม่จำเป็นต้องใช้ช่องว่างภายใน เป็นผลให้พวกเขาจะไม่เสี่ยงที่จะ padding การโจมตีที่เกี่ยวข้อง

  2. จำเป็นต้องมีการเริ่มต้น Vector (IV) สำหรับ GCM IV ไม่ใช่ความลับ ข้อกำหนดเพียงอย่างเดียวคือจะต้องมีการสุ่มหรือคาดเดาไม่ได้ ใน Java SecuredRandomคลาสมีความหมายในการสร้างตัวเลขสุ่มหลอกแบบเข้ารหัสลับที่แข็งแกร่ง อัลกอริธึมการสร้างตัวเลขสุ่มหลอกสามารถระบุได้ในgetInstance()วิธีการ อย่างไรก็ตามตั้งแต่ Java 8 วิธีที่แนะนำคือการใช้getInstanceStrong()วิธีซึ่งจะใช้อัลกอริทึมที่แข็งแกร่งที่สุดที่กำหนดค่าและให้บริการโดยProvider

  3. NIST ขอแนะนำ 96 บิต IV สำหรับ GCM เพื่อส่งเสริมการทำงานร่วมกันประสิทธิภาพและความเรียบง่ายของการออกแบบ

  4. เพื่อให้มั่นใจในความปลอดภัยเพิ่มเติมในการนำไปใช้ต่อไปนี้SecureRandomจะถูกสร้างใหม่หลังจากผลิตทุก ๆ 2 ^ 16 ไบต์ของการสร้างแบบสุ่มหลอกแบบไบต์

  5. ผู้รับต้องรู้ IV เพื่อให้สามารถถอดรหัสข้อความตัวเลขได้ ดังนั้น IV จะต้องมีการถ่ายโอนพร้อมกับข้อความตัวเลข การใช้งานบางอย่างส่ง IV เป็นโฆษณา (ข้อมูลที่เกี่ยวข้อง) ซึ่งหมายความว่าแท็กการตรวจสอบความถูกต้องจะถูกคำนวณทั้งข้อความตัวเลขและ IV อย่างไรก็ตามนั่นไม่จำเป็น IV สามารถถูก pre-pended ได้อย่างง่ายดายด้วยข้อความตัวเลขเพราะถ้า IV ถูกเปลี่ยนแปลงระหว่างการส่งข้อมูลเนื่องจากการโจมตีโดยเจตนาหรือข้อผิดพลาดของระบบเครือข่าย / ไฟล์การตรวจสอบแท็กการรับรองความถูกต้องจะล้มเหลวอยู่ดี

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

  7. ผู้ให้บริการไม่ได้รับการเข้ารหัสอย่างหนักในรหัสตามคำแนะนำทั่วไป

  8. ในที่สุดสำหรับการส่งผ่านเครือข่ายหรือการจัดเก็บคีย์หรือข้อความเข้ารหัสควรถูกเข้ารหัสโดยใช้การเข้ารหัส Base64 รายละเอียดของการ Base64 สามารถพบได้ที่นี่ ควรทำตามวิธีการของ Java 8

อาร์เรย์ไบต์สามารถล้างได้โดยใช้:

Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);

อย่างไรก็ตามใน Java 8 ไม่มีวิธีที่ง่ายในการล้างข้อมูลSecretKeyspecและSecretKeyเนื่องจากการใช้งานของทั้งสองอินเตอร์เฟสนั้นดูเหมือนจะไม่ได้นำวิธีการdestroy()ของอินเตอร์เฟสDestroyableไปใช้ ในรหัสต่อไปนี้มีการเขียนวิธีการแยกเพื่อล้างSecretKeySpecและSecretKeyใช้การสะท้อนกลับ

ควรสร้างคีย์โดยใช้หนึ่งในสองวิธีที่กล่าวถึงด้านล่าง

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

package com.sapbasu.javastudy;

import java.lang.reflect.Field;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Crypto {

  private static final int AUTH_TAG_SIZE = 128; // bits

  // NIST recommendation: "For IVs, it is recommended that implementations
  // restrict support to the length of 96 bits, to
  // promote interoperability, efficiency, and simplicity of design."
  private static final int IV_LEN = 12; // bytes

  // number of random number bytes generated before re-seeding
  private static final double PRNG_RESEED_INTERVAL = Math.pow(2, 16);

  private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";

  private static final List<Integer> ALLOWED_KEY_SIZES = Arrays
      .asList(new Integer[] {128, 192, 256}); // bits

  private static SecureRandom prng;

  // Used to keep track of random number bytes generated by PRNG
  // (for the purpose of re-seeding)
  private static int bytesGenerated = 0;

  public byte[] encrypt(byte[] input, SecretKeySpec key) throws Exception {

    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Length of message cannot be 0");
    }

    if (!ALLOWED_KEY_SIZES.contains(key.getEncoded().length * 8)) {
      throw new IllegalArgumentException("Size of key must be 128, 192 or 256");
    }

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

    byte[] iv = getIV(IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    cipher.init(Cipher.ENCRYPT_MODE, key, gcmParamSpec);
    byte[] messageCipher = cipher.doFinal(input);

    // Prepend the IV with the message cipher
    byte[] cipherText = new byte[messageCipher.length + IV_LEN];
    System.arraycopy(iv, 0, cipherText, 0, IV_LEN);
    System.arraycopy(messageCipher, 0, cipherText, IV_LEN,
        messageCipher.length);
    return cipherText;
  }

  public byte[] decrypt(byte[] input, SecretKeySpec key) throws Exception {
    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Input array cannot be empty");
    }

    byte[] iv = new byte[IV_LEN];
    System.arraycopy(input, 0, iv, 0, IV_LEN);

    byte[] messageCipher = new byte[input.length - IV_LEN];
    System.arraycopy(input, IV_LEN, messageCipher, 0, input.length - IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
    cipher.init(Cipher.DECRYPT_MODE, key, gcmParamSpec);

    return cipher.doFinal(messageCipher);
  }

  public byte[] getIV(int bytesNum) {

    if (bytesNum < 1) throw new IllegalArgumentException(
        "Number of bytes must be greater than 0");

    byte[] iv = new byte[bytesNum];

    prng = Optional.ofNullable(prng).orElseGet(() -> {
      try {
        prng = SecureRandom.getInstanceStrong();
      } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Wrong algorithm name", e);
      }
      return prng;
    });

    if (bytesGenerated > PRNG_RESEED_INTERVAL || bytesGenerated == 0) {
      prng.setSeed(prng.generateSeed(bytesNum));
      bytesGenerated = 0;
    }

    prng.nextBytes(iv);
    bytesGenerated = bytesGenerated + bytesNum;

    return iv;
  }

  private static void clearSecret(Destroyable key)
      throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    Field keyField = key.getClass().getDeclaredField("key");
    keyField.setAccessible(true);
    byte[] encodedKey = (byte[]) keyField.get(key);
    Arrays.fill(encodedKey, Byte.MIN_VALUE);
  }
}

คีย์การเข้ารหัสสามารถสร้างได้สองวิธี:

  • ไม่มีรหัสผ่านใด ๆ

    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
    SecretKey secretKey = keyGen.generateKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
  • ด้วยรหัสผ่าน

    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[32];
    random.nextBytes(salt);
    PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterations, 
       keyLength);
    SecretKeyFactory keyFactory = 
        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey secretKey = keyFactory.generateSecret(keySpec);
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);

อัปเดตตามความคิดเห็น

@MaartenBodewes ชี้ให้เห็นว่าคำตอบของฉันไม่ได้จัดการStringตามที่คำถามนั้นต้องการ Stringดังนั้นฉันจะทำให้ความพยายามที่จะเติมเต็มช่องว่างที่เพียงในกรณีที่มีคนสะดุดเมื่อคำตอบนี้และใบสงสัยเกี่ยวกับการจัดการ

ตามที่ระบุไว้ก่อนหน้าในคำตอบการจัดการข้อมูลที่ละเอียดอ่อนใน a Stringโดยทั่วไปไม่ใช่ความคิดที่ดีเพราะStringไม่เปลี่ยนรูปและทำให้เราไม่สามารถล้างออกได้หลังจากใช้งาน และอย่างที่เราทราบกันดีว่าแม้เมื่อStringไม่มีการอ้างอิงที่ดีนักเก็บขยะก็ไม่รีบเร่งที่จะลบออกจากกอง ดังนั้นการStringยังคงอยู่ในหน่วยความจำสำหรับช่วงเวลาที่ไม่รู้จักแม้ว่าจะไม่สามารถเข้าถึงโปรแกรมได้ ปัญหาที่เกิดขึ้นคือการถ่ายโอนข้อมูลฮีปในช่วงเวลานั้นจะเปิดเผยข้อมูลที่ละเอียดอ่อน ดังนั้นจึงเป็นการดีกว่าเสมอในการจัดการข้อมูลที่สำคัญทั้งหมดในอาร์เรย์ไบต์หรืออาร์เรย์ถ่านแล้วกรอกข้อมูลด้วย 0s เมื่อวัตถุประสงค์ของพวกเขาจะให้บริการ

อย่างไรก็ตามด้วยความรู้ทั้งหมดนั้นหากเรายังคงอยู่ในสถานการณ์ที่ข้อมูลสำคัญที่จะเข้ารหัสอยู่ใน a Stringอันดับแรกเราจำเป็นต้องแปลงเป็นไบต์อาร์เรย์และเรียกใช้encryptและdecryptฟังก์ชั่นที่แนะนำข้างต้น (สามารถป้อนรหัสอื่น ๆ ได้โดยใช้ข้อมูลโค้ดที่ให้ไว้ด้านบน)

A Stringสามารถแปลงเป็นไบต์ด้วยวิธีต่อไปนี้:

byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);

ในฐานะของ Java 8 นั้นStringจะถูกเก็บไว้ในกองด้วยการUTF-16เข้ารหัส อย่างไรก็ตามเราได้ใช้UTF-8ที่นี่เพราะโดยปกติจะใช้พื้นที่น้อยกว่าUTF-16โดยเฉพาะอย่างยิ่งสำหรับอักขระ ASCII

ในทำนองเดียวกันอาร์เรย์ไบต์ที่เข้ารหัสสามารถแปลงเป็นสตริงได้ดังนี้:

String encryptedString = new String(encryptedBytes, StandardCharsets.UTF_8);

1
มากเท่าที่ฉันต้องการลบคำตอบนี้ตามที่ปฏิบัติตามแนวทางปฏิบัติของ crypto ปัจจุบันฉันไม่เห็นการจัดการสตริงใด ๆ เลยทำให้มันเหมือนกับคำอธิบายวิธีการใช้โหมด GCM ดังนั้นจึงไม่สามารถตอบคำถามได้
Maarten Bodewes

1
@MaartenBodewes ขอบคุณมากที่สละเวลาในการตรวจสอบและแบ่งปันความคิดเห็น ฉันเขียนสิ่งนี้ด้วยความเข้าใจว่าการเข้ารหัสการStringใช้ฟังก์ชั่นที่สร้างขึ้นด้านบนนั้นเป็นเรื่องเล็กน้อย อย่างไรก็ตามในการดูครั้งที่สองหลังจากอ่านความคิดเห็นของคุณฉันเข้าใจว่าอาจไม่ชัดเจน ฉันจะแก้ไขเพื่อเพิ่มรายละเอียดเหล่านั้นอย่างแน่นอน
Saptarshi Basu

5

เกี่ยวกับสิ่งนี้:

private static byte[] xor(final byte[] input, final byte[] secret) {
    final byte[] output = new byte[input.length];
    if (secret.length == 0) {
        throw new IllegalArgumentException("empty security key");
    }
    int spos = 0;
    for (int pos = 0; pos < input.length; ++pos) {
        output[pos] = (byte) (input[pos] ^ secret[spos]);
        ++spos;
        if (spos >= secret.length) {
            spos = 0;
        }
    }
    return output;
}

ทำงานได้ดีสำหรับฉันและค่อนข้างกะทัดรัด


จะเกิดอะไรขึ้นหากพารามิเตอร์รายการลับ == null หรืออินพุต == null ทำงานกับไบต์ค่อนข้างแล้วกับสตริงก็โอเค แต่ไม่เกี่ยวข้องในกรณีของฉัน .. สิ่งเดียวที่สำคัญคือต้องอ่านและถอดรหัสได้กับอุปกรณ์ใด ๆ ในการเข้ารหัสตัวอักษรที่เป็นไปได้ ...
ante.sabo

@ ante.sabo เห็นได้ชัดว่าจะโยน NPE นี่เป็นสิ่งเดียวที่จะทำกับ NULLs
Miha_x64

ตราบใดที่input.length <= secret.lengthถือและไม่เคยใช้อีกครั้งนี้มีความปลอดภัยและที่เรียกว่าsecret one-time-padในกรณีinput.length > secret.lengthนี้เป็นตัวแปรของVigenère cipherและถือว่าอ่อนแอมาก
trichner

5

คุณสามารถใช้Jasypt

ด้วย Jasypt การเข้ารหัสและการตรวจสอบรหัสผ่านสามารถทำได้ง่ายเหมือน ...

StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
textEncryptor.setPassword(myEncryptionPassword);

การเข้ารหัสลับ:

String myEncryptedText = textEncryptor.encrypt(myText);

ถอดรหัส:

String plainText = textEncryptor.decrypt(myEncryptedText);

Gradle:

compile group: 'org.jasypt', name: 'jasypt', version: '1.9.2'

คุณสมบัติ:

Jasypt มอบเทคนิคการเข้ารหัสทิศทางเดียวและข้อมูลแบบสองทิศทางได้ง่าย

Open API สำหรับใช้กับผู้ให้บริการ JCE ใด ๆ และไม่เพียง แต่เป็นค่าเริ่มต้น Java VM Jasypt สามารถใช้งานได้ง่ายกับผู้ให้บริการที่มีชื่อเสียงเช่น Bouncy Castle เรียนรู้เพิ่มเติม.

ความปลอดภัยที่สูงขึ้นสำหรับรหัสผ่านของผู้ใช้ เรียนรู้เพิ่มเติม.

รองรับการเข้ารหัสไบนารี่ Jasypt อนุญาตให้แยกย่อยและเข้ารหัสไบนารี (อาร์เรย์ไบต์) เข้ารหัสวัตถุหรือไฟล์ของคุณเมื่อจำเป็น (เช่นถูกส่งผ่านเน็ต)

รองรับการเข้ารหัสตัวเลข นอกจากข้อความและไบนารีจะช่วยให้สามารถแยกย่อยและเข้ารหัสค่าตัวเลข (BigInteger และ BigDecimal ประเภทตัวเลขอื่น ๆ ได้รับการสนับสนุนเมื่อเข้ารหัสสำหรับการคงอยู่ของไฮเบอร์เนต) เรียนรู้เพิ่มเติม.

เธรดที่ปลอดภัยอย่างสมบูรณ์

รองรับการเข้ารหัสการรวมกลุ่ม / ย่อยเพื่อให้ได้ประสิทธิภาพสูงในระบบมัลติโปรเซสเซอร์ / มัลติคอร์

รวมไลบรารี่รุ่นไลท์เวท ("ไลต์") เพื่อความสะดวกในการจัดการในสภาพแวดล้อมที่ จำกัด ขนาดเช่นแพลตฟอร์มมือถือ

จัดเตรียมเครื่องมือการเข้ารหัสที่ง่ายและไม่มีการกำหนดค่าสำหรับผู้ใช้ใหม่กับการเข้ารหัสและยังมีเครื่องมือการเข้ารหัสมาตรฐานที่กำหนดค่าได้สูงสำหรับผู้ใช้ไฟฟ้า

ไฮเบอร์เนต 3 และ 4 ทางเลือกเพิ่มเติมสำหรับการคงอยู่ของฟิลด์ของเอนทิตีที่แมปของคุณในลักษณะการเข้ารหัส การเข้ารหัสของเขตข้อมูลถูกกำหนดไว้ในไฟล์การทำแผนที่ไฮเบอร์เนตและยังคงโปร่งใสสำหรับส่วนที่เหลือของแอปพลิเคชัน (มีประโยชน์สำหรับข้อมูลส่วนบุคคลที่ละเอียดอ่อนฐานข้อมูลที่มีผู้ใช้ที่เปิดใช้งานการอ่านหลาย ... เข้ารหัสข้อความไบนารีตัวเลขบูลีนวันที่ ... เรียนรู้เพิ่มเติม

สามารถผนวกรวมเข้ากับแอปพลิเคชัน Spring ได้อย่างลงตัวพร้อมคุณสมบัติการรวมเฉพาะสำหรับ Spring 2, Spring 3.0 และ Spring 3.1 เครื่องขุดและเครื่องเข้ารหัสทั้งหมดใน jasypt ได้รับการออกแบบให้ใช้งานได้ง่าย (อินสแตนซ์, การพึ่งพา ... ฉีดจากสปริง) และเนื่องจากความปลอดภัยในการใช้เธรดจึงสามารถใช้งานได้โดยไม่ต้องกังวลเกี่ยวกับการซิงโครไนซ์ในสภาพแวดล้อมแบบซิงเกิลตันเช่น Spring เรียนรู้เพิ่มเติม: Spring 2, Spring 3.0, Spring 3.1

Spring Security (ชื่อเดิม Acegi Security) เป็นทางเลือกในการรวมการเข้ารหัสรหัสผ่านและงานที่ตรงกันสำหรับกรอบความปลอดภัยปรับปรุงความปลอดภัยของรหัสผ่านของผู้ใช้โดยใช้กลไกการเข้ารหัสรหัสผ่านที่ปลอดภัยยิ่งขึ้นและให้การกำหนดค่าและการควบคุมระดับสูง เรียนรู้เพิ่มเติม.

จัดเตรียมฟังก์ชันการทำงานขั้นสูงสำหรับการเข้ารหัสไฟล์การกำหนดค่าทั้งหมดหรือบางส่วนของแอปพลิเคชันรวมถึงข้อมูลที่ละเอียดอ่อนเช่นรหัสผ่านฐานข้อมูล รวมการกำหนดค่าที่เข้ารหัสไว้เข้ากับแอปพลิเคชันแบบธรรมดาสปริงและ / หรือไฮเบอร์เนต เรียนรู้เพิ่มเติม.

จัดเตรียมเครื่องมือ CLI (Command Line Interface) ที่ใช้งานง่ายเพื่อให้นักพัฒนาสามารถกำหนดค่าเริ่มต้นข้อมูลที่เข้ารหัสและรวมถึงการดำเนินการเข้ารหัส / ถอดรหัส / แยกย่อยในงานบำรุงรักษาหรือสคริปต์ เรียนรู้เพิ่มเติม.

รวมเข้ากับ Apache Wicket เพื่อการเข้ารหัส URL ที่มีประสิทธิภาพยิ่งขึ้นในแอปพลิเคชันที่ปลอดภัยของคุณ

คำแนะนำที่ครอบคลุมและเอกสาร javadoc เพื่อให้นักพัฒนาสามารถเข้าใจสิ่งที่พวกเขาทำกับข้อมูลของพวกเขาได้ดีขึ้น

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

ความสามารถในการกำหนดค่าระดับสูงมาก: ผู้พัฒนาสามารถใช้ลูกเล่นเช่นสั่งให้ "ผู้เข้ารหัส" ถามเช่นเซิร์ฟเวอร์ HTTPS ระยะไกลสำหรับรหัสผ่านที่จะใช้สำหรับการเข้ารหัส ช่วยให้คุณสามารถตอบสนองความต้องการด้านความปลอดภัยของคุณได้


1
แต่ความปลอดภัยJasyptมีอะไรบ้าง ฉันไม่สามารถหาได้จากเว็บไซต์ของพวกเขา ไม่สามารถแยกแยะได้ภายใต้การโจมตีแบบเลือกข้อความธรรมดา? ความซื่อสัตย์? การรักษาความลับ?
trichner

4

นี่คือการดำเนินการของฉันจาก meta64.com เป็น Spring Singleton หากคุณต้องการสร้างอินสแตนซ์ของ ciper สำหรับการโทรแต่ละครั้งที่ใช้งานได้และจากนั้นคุณสามารถลบการโทรที่ 'ซิงโครไนซ์' ได้ แต่ระวัง 'cipher' จะไม่ปลอดภัยต่อเธรด

import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("singleton")
public class Encryptor {

    @Value("${aeskey}")
    private String keyStr;

    private Key aesKey = null;
    private Cipher cipher = null;

    synchronized private void init() throws Exception {
        if (keyStr == null || keyStr.length() != 16) {
            throw new Exception("bad aes key configured");
        }
        if (aesKey == null) {
            aesKey = new SecretKeySpec(keyStr.getBytes(), "AES");
            cipher = Cipher.getInstance("AES");
        }
    }

    synchronized public String encrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        return toHexString(cipher.doFinal(text.getBytes()));
    }

    synchronized public String decrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.DECRYPT_MODE, aesKey);
        return new String(cipher.doFinal(toByteArray(text)));
    }

    public static String toHexString(byte[] array) {
        return DatatypeConverter.printHexBinary(array);
    }

    public static byte[] toByteArray(String s) {
        return DatatypeConverter.parseHexBinary(s);
    }

    /*
     * DO NOT DELETE
     * 
     * Use this commented code if you don't like using DatatypeConverter dependency
     */
    // public static String toHexStringOld(byte[] bytes) {
    // StringBuilder sb = new StringBuilder();
    // for (byte b : bytes) {
    // sb.append(String.format("%02X", b));
    // }
    // return sb.toString();
    // }
    //
    // public static byte[] toByteArrayOld(String s) {
    // int len = s.length();
    // byte[] data = new byte[len / 2];
    // for (int i = 0; i < len; i += 2) {
    // data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i +
    // 1), 16));
    // }
    // return data;
    // }
}

3
สิ่งนี้จะเข้ารหัสด้วยโหมด ECB ซึ่งน่ากลัว คุณควรตั้งค่าโหมด CBC อย่างน้อยหรือโหมด GCM
Konstantino Sparakis

ขอบคุณสำหรับคำแนะนำ Konstantinto ฉัน googled และพบรหัสบางส่วนที่ใช้ "AES / CBC / PKCS5Padding" เป็นสตริง Init สำหรับ Cipher แทนที่จะเป็นเพียงแค่ "AES" แต่ฉันจะดูเพิ่มเติม หรือถ้าคุณต้องการคุณสามารถให้การแก้ไขจริงเพื่อให้ผู้อื่นสามารถเห็นวิธีที่ดีกว่า อย่างไรก็ตามนอกเหนือจากรายละเอียด CBC ฉันเชื่อว่าวิธีการแก้ปัญหาของฉันนั้นง่ายและปลอดภัยที่สุดและสมควรที่จะได้รับการสนับสนุนเหนือสิ่งอื่น

ใช่แล้วไม่ต้องกังวล Crypto เป็นวิชาที่ซับซ้อน น่าเสียดายที่การติดตั้งทุกครั้งในหน้านี้ใช้งานไม่ได้และน่าเสียดายที่หน้าแรกที่ปรากฏขึ้นเมื่อใช้ google เพื่อค้นหา "วิธีการเข้ารหัสด้วยจาวา" เมื่อฉันมีโอกาสฉันจะพยายามแก้ไขทั้งหมด
Konstantino Sparakis

ตัวอย่างของฉันเหมือนกับสิ่งนี้: docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/ … ยกเว้นว่าฉันต้องการ Cipher.getInstance ("AES / ECB / PKCS5Padding"); รหัสของฉันสันนิษฐานว่ามีไฟล์คุณสมบัติบางอย่างที่มีคีย์การเข้ารหัสยาว 16 ไบต์อย่างสมบูรณ์ แต่สำหรับการเข้ารหัสสตริงจากรหัสผ่าน 'ผู้ใช้ระบุ' หน้า oracle (ลิงค์ด้านบน) แสดงวิธีการทำเช่นนั้น

1
ดังนั้นปัญหาของ ECB ก็คือมันมีความเสี่ยงอย่างยิ่งต่อการวิเคราะห์ความถี่ มีตัวอย่างที่โด่งดังของเพนกวินลินุกซ์blog.filippo.io/the-ecb-penguinดูว่าภาพนั้นเข้ารหัสอย่างไรคุณยังสามารถบอกได้ว่ามันเป็นนกเพนกวิน ฉันไปข้างหน้าและเขียนความคิดของฉันในหัวข้อด้านล่าง :) stackoverflow.com/a/43779197/2607972
Konstantino Sparakis

4

นี่เป็นทางออกที่ง่ายมีเพียงjava.*และjavax.crypto.*การอ้างอิงสำหรับการเข้ารหัสของไบต์ให้การรักษาความลับและความสมบูรณ์ มันจะแยกไม่ออกภายใต้การโจมตีแบบข้อความธรรมดาที่เลือกไว้สำหรับข้อความสั้น ๆ ในลำดับกิโลไบต์

มันใช้AESในGCMโหมดที่ไม่มีช่องว่างภายใน, คีย์ 128 บิตนั้นได้มาจากPBKDF2การวนซ้ำจำนวนมากและเกลือสแตติกจากรหัสผ่านที่ให้ไว้ สิ่งนี้ทำให้แน่ใจได้ว่าการบังคับใช้รหัสผ่านที่โหดร้ายนั้นยากและแจกจ่ายเอนโทรปีให้กับคีย์ทั้งหมด

มีการสร้างเวกเตอร์เริ่มต้นแบบสุ่ม (IV) และจะถูกนำไปรวมกับ ciphertext นอกจากนี้ไบต์คงที่0x01จะถูกเติมไว้เป็นไบต์แรกเป็น 'รุ่น'

ข้อความทั้งหมดเข้าสู่รหัสตรวจสอบข้อความ (MAC) ที่สร้างโดย AES/GCMที่สร้างโดย

ที่นี่จะเป็นศูนย์การเข้ารหัสการพึ่งพาจากภายนอกให้การรักษาความลับและความสมบูรณ์ :

package ch.n1b.tcrypt.utils;

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * This class implements AES-GCM symmetric key encryption with a PBKDF2 derived password.
 * It provides confidentiality and integrity of the plaintext.
 *
 * @author Thomas Richner
 * @created 2018-12-07
 */
public class AesGcmCryptor {

    // /crypto/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode
    private static final byte VERSION_BYTE = 0x01;
    private static final int VERSION_BYTE_LENGTH = 1;
    private static final int AES_KEY_BITS_LENGTH = 128;


    // fixed AES-GCM constants
    private static final String GCM_CRYPTO_NAME = "AES/GCM/NoPadding";
    private static final int GCM_IV_BYTES_LENGTH = 12;
    private static final int GCM_TAG_BYTES_LENGTH = 16;

    // can be tweaked, more iterations = more compute intensive to brute-force password
    private static final int PBKDF2_ITERATIONS = 1024;

    // protects against rainbow tables
    private static final byte[] PBKDF2_SALT = hexStringToByteArray("4d3fe0d71d2abd2828e7a3196ea450d4");

    public String encryptString(char[] password, String plaintext) throws CryptoException {

        byte[] encrypted = null;
        try {
            encrypted = encrypt(password, plaintext.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException //
                | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException //
                | InvalidKeySpecException e) {
            throw new CryptoException(e);
        }
        return byteArrayToHexString(encrypted);
    }

    public String decryptString(char[] password, String ciphertext)
            throws CryptoException {

        byte[] ct = hexStringToByteArray(ciphertext);
        byte[] plaintext = null;
        try {
            plaintext = decrypt(password, ct);
        } catch (AEADBadTagException e) {
            throw new CryptoException(e);
        } catch ( //
                NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeySpecException //
                        | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException //
                        | BadPaddingException e) {
            throw new CryptoException(e);
        }
        return new String(plaintext, StandardCharsets.UTF_8);
    }

    /**
     * Decrypts an AES-GCM encrypted ciphertext and is
     * the reverse operation of {@link AesGcmCryptor#encrypt(char[], byte[])}
     *
     * @param password   passphrase for decryption
     * @param ciphertext encrypted bytes
     * @return plaintext bytes
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws IllegalArgumentException           if the length or format of the ciphertext is bad
     * @throws CryptoException
     */
    public byte[] decrypt(char[] password, byte[] ciphertext)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        // input validation
        if (ciphertext == null) {
            throw new IllegalArgumentException("ciphertext cannot be null");
        }

        if (ciphertext.length <= VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH + GCM_TAG_BYTES_LENGTH) {
            throw new IllegalArgumentException("ciphertext too short");
        }

        // the version must match, we don't decrypt other versions
        if (ciphertext[0] != VERSION_BYTE) {
            throw new IllegalArgumentException("wrong version: " + ciphertext[0]);
        }

        // input seems legit, lets decrypt and check integrity

        // derive key from password
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);

        // init cipher
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec params = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8,
                ciphertext,
                VERSION_BYTE_LENGTH,
                GCM_IV_BYTES_LENGTH
        );
        cipher.init(Cipher.DECRYPT_MODE, key, params);

        final int ciphertextOffset = VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH;

        // add version and IV to MAC
        cipher.updateAAD(ciphertext, 0, ciphertextOffset);

        // decipher and check MAC
        return cipher.doFinal(ciphertext, ciphertextOffset, ciphertext.length - ciphertextOffset);
    }

    /**
     * Encrypts a plaintext with a password.
     * <p>
     * The encryption provides the following security properties:
     * Confidentiality + Integrity
     * <p>
     * This is achieved my using the AES-GCM AEAD blockmode with a randomized IV.
     * <p>
     * The tag is calculated over the version byte, the IV as well as the ciphertext.
     * <p>
     * Finally the encrypted bytes have the following structure:
     * <pre>
     *          +-------------------------------------------------------------------+
     *          |         |               |                             |           |
     *          | version | IV bytes      | ciphertext bytes            |    tag    |
     *          |         |               |                             |           |
     *          +-------------------------------------------------------------------+
     * Length:     1B        12B            len(plaintext) bytes            16B
     * </pre>
     * Note: There is no padding required for AES-GCM, but this also implies that
     * the exact plaintext length is revealed.
     *
     * @param password  password to use for encryption
     * @param plaintext plaintext to encrypt
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeySpecException
     */
    public byte[] encrypt(char[] password, byte[] plaintext)
            throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
            InvalidKeySpecException {

        // initialise random and generate IV (initialisation vector)
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);
        final byte[] iv = new byte[GCM_IV_BYTES_LENGTH];
        SecureRandom random = SecureRandom.getInstanceStrong();
        random.nextBytes(iv);

        // encrypt
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);

        // add IV to MAC
        final byte[] versionBytes = new byte[]{VERSION_BYTE};
        cipher.updateAAD(versionBytes);
        cipher.updateAAD(iv);

        // encrypt and MAC plaintext
        byte[] ciphertext = cipher.doFinal(plaintext);

        // prepend VERSION and IV to ciphertext
        byte[] encrypted = new byte[1 + GCM_IV_BYTES_LENGTH + ciphertext.length];
        int pos = 0;
        System.arraycopy(versionBytes, 0, encrypted, 0, VERSION_BYTE_LENGTH);
        pos += VERSION_BYTE_LENGTH;
        System.arraycopy(iv, 0, encrypted, pos, iv.length);
        pos += iv.length;
        System.arraycopy(ciphertext, 0, encrypted, pos, ciphertext.length);

        return encrypted;
    }

    /**
     * We derive a fixed length AES key with uniform entropy from a provided
     * passphrase. This is done with PBKDF2/HMAC256 with a fixed count
     * of iterations and a provided salt.
     *
     * @param password passphrase to derive key from
     * @param salt     salt for PBKDF2 if possible use a per-key salt, alternatively
     *                 a random constant salt is better than no salt.
     * @param keyLen   number of key bits to output
     * @return a SecretKey for AES derived from a passphrase
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    private SecretKey deriveAesKey(char[] password, byte[] salt, int keyLen)
            throws NoSuchAlgorithmException, InvalidKeySpecException {

        if (password == null || salt == null || keyLen <= 0) {
            throw new IllegalArgumentException();
        }
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, keyLen);
        SecretKey pbeKey = factory.generateSecret(spec);

        return new SecretKeySpec(pbeKey.getEncoded(), "AES");
    }

    /**
     * Helper to convert hex strings to bytes.
     * <p>
     * May be used to read bytes from constants.
     */
    private static byte[] hexStringToByteArray(String s) {

        if (s == null) {
            throw new IllegalArgumentException("Provided `null` string.");
        }

        int len = s.length();
        if (len % 2 != 0) {
            throw new IllegalArgumentException("Invalid length: " + len);
        }

        byte[] data = new byte[len / 2];
        for (int i = 0; i < len - 1; i += 2) {
            byte b = (byte) toHexDigit(s, i);
            b <<= 4;
            b |= toHexDigit(s, i + 1);
            data[i / 2] = b;
        }
        return data;
    }

    private static int toHexDigit(String s, int pos) {
        int d = Character.digit(s.charAt(pos), 16);
        if (d < 0) {
            throw new IllegalArgumentException("Cannot parse hex digit: " + s + " at " + pos);
        }
        return d;
    }

    private static String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }

    public class CryptoException extends Exception {

        public CryptoException(Throwable cause) {
            super(cause);
        }
    }
}

นี่คือโครงการทั้งหมดที่มี CLI ที่ดี: https://github.com/trichner/tcrypt

แก้ไข:ตอนนี้มีความเหมาะสมencryptStringและdecryptString


นี่เป็นเรื่องเหลือเชื่อ ขอบคุณ! ฉันเรียนรู้มากมายจากโค้ดของคุณและหลังจากสร้างคลาส BadVersionException Exception โค้ดของคุณจะทำงานได้อย่างสมบูรณ์ในครั้งแรก ยอดเยี่ยม !!
Morkus

ฉันชอบความพยายามนี้ ที่กล่าวว่า ... เกลือควรสุ่มไม่คงที่ การทำซ้ำอาจไม่คงที่เช่นกัน GCM ได้รวม IV ในการคำนวณแท็กแล้ว มันไม่ได้มีหมายเลขรุ่นแม้ว่า คุณไม่ควรระบุผู้ให้บริการสำหรับการพกพาตัวเลือก "SunJCE" จะเป็นค่าเริ่มต้นบนแพลตฟอร์มที่รองรับ รหัสนี้ไม่ได้มีการใด ๆ จัดการสตริงข้อความซึ่งเป็นสิ่งจำเป็นสำหรับการนี้คำถามโดยเฉพาะอย่างยิ่ง
Maarten Bodewes

เอาล่ะฉันทำความสะอาดมันอีกสักหน่อยแล้วเพิ่มสิ่งที่ต้องการencryptStringและdecryptString:)
trichner

มันใช้งานได้ดีมาก สำหรับรหัส ควรสังเกตว่ารหัสนี้ต้องใช้ API 19 (Kit Kat) ขึ้นไปเพื่อให้ทำงานได้อย่างถูกต้อง
PGMacDesign

3

ฉันจะพิจารณาใช้สิ่งต่าง ๆ เช่นhttps://www.bouncycastle.org/มันเป็นห้องสมุดที่สร้างไว้ล่วงหน้าซึ่งอนุญาตให้คุณเข้ารหัสสิ่งที่คุณต้องการด้วย Ciphers ที่แตกต่างกันจำนวนมากฉันเข้าใจว่าคุณต้องการปกป้องจากการสอดแนม แต่ถ้าคุณจริงๆ ต้องการปกป้องข้อมูลโดยใช้ Base64 จะไม่ปกป้องคุณจริงๆ


1
เพียงแค่แนะนำไลบรารี crypto แบบสุ่มที่มี ciphers ไม่ใช่คำตอบสำหรับคำถาม นอกจากนั้นทำไมไม่ใช้ ciphers แบบบิวด์อิน?
Maarten Bodewes

2

นี่คือลิงค์บางส่วนที่คุณสามารถอ่านได้ว่า Java รองรับอะไร

การเข้ารหัส / ถอดรหัสกระแสข้อมูล

ตัวอย่างนี้แสดงวิธีการเข้ารหัส (ใช้อัลกอริทึมการเข้ารหัสแบบสมมาตรเช่น AES, Blowfish, RC2, 3DES ฯลฯ ) ข้อมูลจำนวนมาก ข้อมูลถูกส่งเป็นชิ้นไปยังหนึ่งในวิธีการเข้ารหัส: EncryptBytes, EncryptString, EncryptBytesENC หรือ EncryptStringENC (ชื่อเมธอดระบุชนิดของอินพุต (สตริงหรือไบต์อาร์เรย์) และชนิดส่งคืน (สตริงที่เข้ารหัสหรืออาร์เรย์ไบต์) คุณสมบัติ FirstChunk และ LastChunk ถูกใช้เพื่อระบุว่าก้อนเป็นครั้งแรกกลางหรือครั้งสุดท้ายในสตรีม ที่จะเข้ารหัสโดยค่าเริ่มต้นทั้ง FirstChunk และ LastChunk เท่ากันจริง - หมายถึงข้อมูลที่ส่งเป็นจำนวนเงินทั้งหมด

JCERefGuide

ตัวอย่างการเข้ารหัส Java


ใช่มีการเข้ารหัสโดย Java การเข้ารหัสของสตรีมไม่ใช่สิ่งที่ขอด้วย
Maarten Bodewes

2

เช่นเดียวกับที่ผู้ชายหลายคนได้บอกไปแล้วคุณควรใช้ Cypher มาตรฐานที่ใช้มากเกินไปเช่น DES หรือ AES

ตัวอย่างง่ายๆของวิธีการที่คุณสามารถเข้ารหัสและถอดรหัสสตริงใน java ใช้AES

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class EncryptorDemo {

    public static String encrypt(String key, String randomVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted text: "  + Base64.encodeBase64String(encrypted));
            return Base64.encodeBase64String(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String randomVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] originalText = cipher.doFinal(Base64.decodeBase64(encrypted));
            System.out.println("decrypted text: "  + new String(originalText));
            return new String(originalText);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        String key = "JavasEncryptDemo"; // 128 bit key
        String randomVector = "RandomJavaVector"; // 16 bytes IV
        decrypt(key, randomVector, encrypt(key, randomVector, "Anything you want to encrypt!"));

    }
}

CBC ไม่ใช่โหมดปลอดภัยอีกต่อไป การบุนวมมีความเสี่ยงที่จะทำให้ช่องว่างภายในของ Oracle โจมตี การจัดการคีย์และข้อความใน String ไม่ปลอดภัย พวกเขาจะอยู่ในสระ String และปรากฏในกองทิ้ง
Saptarshi Basu

2
ขอบคุณความคิดเห็น นี่เป็นตัวอย่างง่ายๆของวิธีการเข้ารหัสและถอดรหัสของ Java เมื่อผู้ใช้ถาม คำถามถูกถามเมื่อ 9 ปีที่แล้วและได้รับคำตอบจากสิ่งนั้น ขอบคุณ
viveknaskar

2
ใช่นี่เป็นวิธีง่ายๆในการแนะนำการเข้ารหัส / ถอดรหัส ทำงานเหมือนเสน่ห์สำหรับฉัน .... ขอบคุณ
Codewrapper

0

นี่คือโซลูชันคัดลอก / วาง ฉันยังแนะนำให้อ่านและออกเสียงลงคะแนนสำหรับคำตอบของ @ Konstantinoแม้ว่าจะไม่ได้ให้รหัสใด ๆ ก็ตาม เวกเตอร์เริ่มต้น (IV) เป็นเหมือนเกลือ - ไม่จำเป็นต้องเก็บเป็นความลับ ฉันใหม่กับ GCM และเห็นได้ชัดว่า AAD เป็นทางเลือกและใช้ในบางสถานการณ์เท่านั้น SECRET_KEY_BASEตั้งค่าคีย์ในตัวแปรสภาพแวดล้อม ใช้สิ่งที่คล้ายกับKeePassเพื่อสร้างรหัสผ่าน 32 ตัวอักษร วิธีแก้ปัญหานี้ถูกสร้างขึ้นหลังจากโซลูชัน Ruby ของฉัน

    public static String encrypt(String s) {
        try {
            byte[] input = s.getBytes("UTF-8");
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            // generate IV
            SecureRandom secureRandom = SecureRandom.getInstanceStrong();
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            secureRandom.nextBytes(ivBytes);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes); // 96 bit tag length
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
            // generate AAD
//          byte[] aadBytes = new byte[cipher.getBlockSize()];
//          secureRandom.nextBytes(aadBytes);
//          cipher.updateAAD(aadBytes);
            // encrypt
            byte[] encrypted = cipher.doFinal(input);
            byte[] returnBytes = new byte[ivBytes.length + encrypted.length];
//          byte[] returnBytes = new byte[ivBytes.length + aadBytes.length + encrypted.length];
            System.arraycopy(ivBytes, 0, returnBytes, 0, ivBytes.length);
//          System.arraycopy(aadBytes, 0, returnBytes, ivBytes.length, aadBytes.length);
            System.arraycopy(encrypted, 0, returnBytes, ivBytes.length, encrypted.length);
//          System.arraycopy(encrypted, 0, returnBytes, ivBytes.length+aadBytes.length, encrypted.length);
            String encryptedString = Base64.getEncoder().encodeToString(returnBytes);
            return encryptedString;
        } catch (UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "encrypt()", "Could not encrypt string: " + e.getMessage());
            return null;
        }
    }

    public static String decrypt(String s) {
        if (s == null || s.length() == 0) return "";
        try {
            byte[] encrypted = Base64.getDecoder().decode(s);
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            System.arraycopy(encrypted, 0, ivBytes, 0, ivBytes.length);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
//          cipher.updateAAD(encrypted, ivBytes.length, cipher.getBlockSize());
            byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize(), encrypted.length - cipher.getBlockSize());
//          byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize()*2, encrypted.length - cipher.getBlockSize()*2);
            String decryptedString = new String(decrypted, "UTF-8");
            return decryptedString;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "decrypt()", "Could not decrypt string: " + e.getMessage());
            return null;
        }
    }

นี่คือตัวอย่าง:

    String s = "This is a test.";
    String enc = Utils.encrypt(s);
    System.out.println(enc);
    // fQHfYjbD+xAuN5XzH2ojk/EWNeKXUrKRSfx8LU+5dpuKkM/pueCMBjKCZw==
    String dec = Utils.decrypt(enc);
    System.out.println(dec);
    // This is a test.

-4

คุณอาจต้องการพิจารณาเครื่องมืออัตโนมัติบางอย่างในการสร้างรหัสการเข้ารหัส / ถอดรหัสเช่น https://www.stringencrypt.com/java-encryption/

มันสามารถสร้างรหัสการเข้ารหัสและถอดรหัสที่แตกต่างกันในแต่ละครั้งสำหรับการเข้ารหัสสตริงหรือไฟล์

มันค่อนข้างมีประโยชน์เมื่อพูดถึงการเข้ารหัสสตริงที่รวดเร็วโดยไม่ต้องใช้ RSA, AES และอื่น ๆ

ตัวอย่างผลลัพธ์:

// encrypted with https://www.stringencrypt.com (v1.1.0) [Java]
// szTest = "Encryption in Java!"
String szTest = "\u9E3F\uA60F\uAE07\uB61B\uBE1F\uC62B\uCE2D\uD611" +
                "\uDE03\uE5FF\uEEED\uF699\uFE3D\u071C\u0ED2\u1692" +
                "\u1E06\u26AE\u2EDC";

for (int iatwS = 0, qUJQG = 0; iatwS < 19; iatwS++)
{
        qUJQG = szTest.charAt(iatwS);
        qUJQG ++;
        qUJQG = ((qUJQG << 5) | ( (qUJQG & 0xFFFF) >> 11)) & 0xFFFF;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 6) | (qUJQG << 10)) & 0xFFFF;
        qUJQG ^= iatwS;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 3) | (qUJQG << 13)) & 0xFFFF;
        qUJQG ^= 0xFFFF;
        qUJQG ^= 0xB6EC;
        qUJQG = ((qUJQG << 8) | ( (qUJQG & 0xFFFF) >> 8)) & 0xFFFF;
        qUJQG --;
        qUJQG = (((qUJQG & 0xFFFF) >> 5) | (qUJQG << 11)) & 0xFFFF;
        qUJQG ++;
        qUJQG ^= 0xFFFF;
        qUJQG += iatwS;
        szTest = szTest.substring(0, iatwS) + (char)(qUJQG & 0xFFFF) + szTest.substring(iatwS + 1);
}

System.out.println(szTest);

เราใช้มันตลอดเวลาใน บริษัท ของเรา


นี่คือความปลอดภัยผ่านความสับสนและไม่ปลอดภัยจริงๆ
Chloe

คำถามนี้กำลังถามถึงการเข้ารหัสลับการเข้ารหัสลับแบบแรงสมัยใหม่เช่น AES ไม่ใช่แค่การทำให้สับสนเพื่อสร้างสตริงที่ยากขึ้นในการแยกแบบคงที่ สิ่งนี้ไม่ได้ดูเหมือนจะรักษาสถานะระหว่างตัวละครเพื่อให้ไวต่อการวิเคราะห์ความถี่ ( รหัสแทนตัวอักษรเดี่ยวยกเว้นรหัส codepoints UTF-16 มากกว่าตัวอักษรละติน แต่ถ้าคุณใช้กับข้อความ ASCII ภาษาอังกฤษคุณจะได้รับค่าอักขระ 16 บิตที่ไม่ซ้ำกันเท่านั้นเว้นแต่ว่าฉันเข้าใจผิดนี่)
Peter Cordes

-4
String s1="arshad"; 
char[] s2=s1.toCharArray(); 
int s3= s2.length; 

  System.out.println(s3);
 int i=0; 

// for(int j=0;j<s3;j++) 
// System.out.println(s2[j]); 

for(i=0;i<((s3)/2);i++) { 

char z,f=10; 
z=(char) (s2[i] * f); 
s2[i]=s2[(s3-1)-i]; 
s2[(s3-1)-i]=z; 

String b=new String(s2);

 print(b);  }

อย่างเป็นทางการมันเข้ารหัสข้อมูลในรูปแบบที่อ่านไม่ได้ ในการถอดรหัสใช้รหัสเดียวกัน และเปลี่ยน s [i] * f เป็น s [I] / f
Arshad shaik

นี่คือความปลอดภัยผ่านความสับสนและไม่ปลอดภัยจริงๆ
Chloe

-5
public static String encryptParams(String myTextInput) {

        String myKey = "40674244454045cb9a70040a30e1c007";
        String myVector = "@1B2c3D4e5F6g7H8";

        String encData = "";

        try{
            JavaEncryprtionUtil encUtil = new JavaEncryprtionUtil();
            encData = Base64.encodeToString(encUtil.encrypt(myTextInput.getBytes("UTF-8"), myKey.getBytes("UTF-8"), myVector.getBytes("UTF-8")),Base64.DEFAULT);
            System.out.println(encData);
        }catch(NoSuchAlgorithmException ex){
            ex.printStackTrace();
        }catch(NoSuchPaddingException ex){
            ex.printStackTrace();
        }catch(InvalidKeyException ex){
            ex.printStackTrace();
        }catch(InvalidAlgorithmParameterException ex){
            ex.printStackTrace();
        }catch(IllegalBlockSizeException ex){
            ex.printStackTrace();
        }catch(BadPaddingException ex){
            ex.printStackTrace();
        }catch(UnsupportedEncodingException ex){
            ex.printStackTrace();
        }

        return encData;
    }

1
JavaEncryprtionUtil เป็นส่วนหนึ่งของ JDK API หรือไม่ ถ้าไม่ใช่คุณควรสะกดชื่อไลบรารี
นักเรียนตัวน้อยของแฟร์มาต์

4
ไม่พบคลาสนั้น รู้สึกเหมือนคำตอบถูกสร้างขึ้น
james.garriss
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.