V2EX-PGP-MSG

通过 PGP 间接在 V2EX 上实现私信功能。

  1. // ==UserScript==
  2. // @name V2EX-PGP-MSG
  3. // @namespace http://shendaowu.freevar.com/
  4. // @version 0.2
  5. // @description 通过 PGP 间接在 V2EX 上实现私信功能。
  6. // @author shendaowu
  7. // @match https://www.v2ex.com/*
  8. // @require https://cdnjs.cloudflare.com/ajax/libs/openpgp/4.10.7/openpgp.min.js
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. let $ = window.jQuery;
  16. let openpgp = window.openpgp;
  17.  
  18. const markStr = "Greasy Fork镜像 搜索 V2EX-PGP-MSG,让加密解密更方便。";
  19.  
  20. let bodyText = $("body")[0].innerText;
  21. let re = new RegExp("-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----", "gms");
  22. let encryptedMessages = bodyText.match(re);
  23. if(encryptedMessages){
  24. let bodyHTML = $("body")[0].innerHTML;
  25. re = new RegExp("(-----BEGIN PGP MESSAGE-----)(.*?-----END PGP MESSAGE-----)");
  26. for(let i = 0; i < encryptedMessages.length; i++){
  27. bodyHTML = bodyHTML.replace(re, '-----TMP MARK-----$2<br><div style="border: 1px solid;white-space: pre-line" id="decrypted_messages_' + i + '"/>如果一直显示此消息表明很可能消息不是用你的公钥加密的。</div>');
  28. }
  29. re = new RegExp("-----TMP MARK-----", "gms");
  30. bodyHTML = bodyHTML.replace(re, "-----BEGIN PGP MESSAGE-----");
  31. $("body")[0].innerHTML = bodyHTML;
  32. }
  33. (async () => {
  34. if(!encryptedMessages) return;
  35. const privateKeyArmored = JSON.parse(localStorage.getItem("pri_key"));
  36. const passphrase = JSON.parse(localStorage.getItem("pgp_password"));
  37. const { keys: [privateKey] } = await openpgp.key.readArmored(privateKeyArmored);
  38. await privateKey.decrypt(passphrase);
  39. for(let i = 0; i < encryptedMessages.length; i++){
  40. const { data: decrypted } = await openpgp.decrypt({
  41. message: await openpgp.message.readArmored(encryptedMessages[i]),
  42. privateKeys: [privateKey]
  43. });
  44. $("#decrypted_messages_" + i).text(decrypted);
  45. }
  46. })();
  47.  
  48. bodyText = $("body")[0].innerText;
  49. re = new RegExp(markStr + ".*?-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----", "gms");
  50. let publicKeys = bodyText.match(re);
  51. if(publicKeys){
  52. re = new RegExp("(" + markStr + ".*?)(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----)", "ms");
  53. for(let i = 0; i < publicKeys.length; i++){
  54. publicKeys[i] = publicKeys[i].replace(re, "$2");
  55. }
  56. let bodyHTML = $("body")[0].innerHTML;
  57. re = new RegExp("(" + markStr + "<br>)(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----)");
  58. for(let i = 0; i < publicKeys.length; i++){
  59. bodyHTML = bodyHTML.replace(re, '$1<button id="use_pub_key_' + i + '" class="use_pub_key" type="button"/>使用以下公钥加密</button><br>$2');
  60. }
  61. $("body")[0].innerHTML = bodyHTML;
  62. $(".use_pub_key").click(function(){
  63. showEncryptMessageDialogAndFillPubKey(publicKeys[this.id.split("_")[3]]);
  64. });
  65. }
  66. $("body").append(
  67. `<div id="encrypt_popup" style="width: 100vw;height: 100vh;background-color: rgba(0, 0, 0, .5);position: fixed;left: 0;top: 0;bottom: 0;right: 0;display: none;justify-content: center;align-items: center;">
  68. <div style="overflow: auto;width: 800px;height: 500px;background-color: #fff;box-sizing: border-box;padding: 10px 30px;color: black;">
  69. <button id="close_encrypt_popup" type="button">关闭</button>
  70. <button id="encrypt_message" type="button">加密</button>
  71. <button id="append_to_reply" type="button">密文附加到回复并关闭弹窗</button>
  72. <br>
  73. 需要加密的消息:
  74. <textarea id="message_to_be_encrypt" style="width: 100%;height: 200px;"></textarea><br>
  75. 密文:
  76. <textarea id="encrypted_message" style="width: 100%;height: 200px;"></textarea><br>
  77. 公钥:
  78. <textarea id="selected_pub_key" style="width: 100%;height: 200px;"></textarea><br>
  79. </div>
  80. </div>`);
  81. $("#close_encrypt_popup").click(function(){$("#encrypt_popup").css("display", "none");});
  82. $("#encrypt_message").click(function(){encryptMessage();});
  83. function showEncryptMessageDialogAndFillPubKey(pubKey){
  84. $("#encrypt_popup").css("display", "flex");
  85. $("#selected_pub_key").val(pubKey);
  86. }
  87. let encryptMessage = async () => {
  88. const publicKeyArmored = $("#selected_pub_key").val();
  89. const { data: encrypted } = await openpgp.encrypt({
  90. message: openpgp.message.fromText($("#message_to_be_encrypt").val()),
  91. publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys
  92. });
  93. $("#encrypted_message").val(encrypted);
  94. };
  95. $("#append_to_reply").click(function(){
  96. $("#reply_content").val($("#reply_content").val() + $("#encrypted_message").val());
  97. $("#encrypt_popup").css("display", "none");
  98. });
  99.  
  100. $('<button id="show_PGP_manager_popup" type="button"/>PGP</button>').insertBefore(".top[href='/']");
  101. $('<button id="show_encrypt_message_dialog" type="button"/>加密</button>').insertBefore(".top[href='/']");
  102. $("#show_encrypt_message_dialog").click(function(){
  103. $("#encrypt_popup").css("display", "flex");
  104. });
  105. $("body").append(
  106. `<div id="PGP_manager_popup" style="width: 100vw;height: 100vh;background-color: rgba(0, 0, 0, .5);position: fixed;left: 0;top: 0;bottom: 0;right: 0;display: none;justify-content: center;align-items: center;">
  107. <div style="overflow: auto;width: 800px;height: 500px;background-color: #fff;box-sizing: border-box;padding: 10px 30px;color: black;">
  108. <button id="close_PGP_manager_popup" type="button">关闭</button>
  109. <button id="generate_key_pair" type="button">生成密钥</button>
  110. <button id="save_key_pair_and_pass" type="button">保存密钥和密码</button>
  111. <button id="load_key_pair_and_pass" type="button">加载密钥和密码</button>
  112. <button id="pgp_help" type="button">使用说明</button>
  113. <br>
  114. 姓名:<input type="text" id="pgp_name" size="40" value="Jon Smith"><br>
  115. 邮箱:<input type="text" id="pgp_email" size="40" value="jon@example.com"><br>
  116. 密码:<input type="text" id="pgp_password" size="40" value="super long and hard to guess secret"><br>
  117. 建议手动备份以下三个密钥。点击保存密钥按钮只会把密钥和密码存储在浏览器的 Local Storage 里。
  118. 保存私钥是为了自动解密密文。Local Storage 里的东西重装浏览器或者清除浏览器历史记录可能会没。
  119. 理论上其他软件生成的公钥和私钥粘贴到这里再保存应该也可以,不过我没试过。<br>
  120. 公钥:
  121. <textarea id="pub_key" style="width: 100%;height: 200px;"></textarea><br>
  122. 私钥:
  123. <textarea id="pri_key" style="width: 100%;height: 200px;"></textarea><br>
  124. 撤销证书:
  125. <textarea id="rev_key" style="width: 100%;height: 200px;"></textarea>
  126. </div>
  127. </div>`);
  128. $("#show_PGP_manager_popup").on('click', function(){$("#PGP_manager_popup").css("display", "flex");});
  129. $("#generate_key_pair").click(function(){genKeyPair();});
  130. $("#close_PGP_manager_popup").click(function(){$("#PGP_manager_popup").css("display", "none");});
  131. $("#save_key_pair_and_pass").click(function(){
  132. let isHasSavedKeyOrPassword = false;
  133. if( localStorage.getItem("pgp_password") != null ||
  134. localStorage.getItem("pub_key") != null ||
  135. localStorage.getItem("pri_key") != null ||
  136. localStorage.getItem("rev_key") != null ){
  137. isHasSavedKeyOrPassword = true;
  138. }
  139. let isClickConfirm = false;
  140. if(isHasSavedKeyOrPassword){
  141. isClickConfirm = confirm("存在已保存的密钥或密钥密码,确定覆盖?");
  142. }
  143. if(!isHasSavedKeyOrPassword || (isHasSavedKeyOrPassword && isClickConfirm)){
  144. localStorage.setItem("pgp_password", JSON.stringify($("#pgp_password").val()));
  145. localStorage.setItem("pub_key", JSON.stringify($("#pub_key").val()));
  146. localStorage.setItem("pri_key", JSON.stringify($("#pri_key").val()));
  147. localStorage.setItem("rev_key", JSON.stringify($("#rev_key").val()));
  148. }
  149. });
  150. $("#load_key_pair_and_pass").click(function(){
  151. $("#pgp_password").val(JSON.parse(localStorage.getItem("pgp_password")));
  152. $("#pub_key").val(JSON.parse(localStorage.getItem("pub_key")));
  153. $("#pri_key").val(JSON.parse(localStorage.getItem("pri_key")));
  154. $("#rev_key").val(JSON.parse(localStorage.getItem("rev_key")));
  155. $("#pgp_name").val("");
  156. $("#pgp_email").val("");
  157. });
  158. let genKeyPair = async () => {
  159. const { privateKeyArmored, publicKeyArmored, revocationCertificate } = await openpgp.generateKey({
  160. userIds: [{ name: $("#pgp_name").val(), email: $("#pgp_email").val() }],
  161. curve: 'ed25519',
  162. passphrase: $("#pgp_password").val()
  163. });
  164. $("#pri_key").val(privateKeyArmored);
  165. $("#pub_key").val(publicKeyArmored);
  166. $("#rev_key").val(revocationCertificate);
  167. };
  168. $("#pgp_help").click(function(){
  169. alert(
  170. `第一步:点击右上角的“PGP”按钮。在弹出的窗口中填入姓名、邮箱和密码。然后点击“生成密钥”按钮。然后点击“保存密钥和密码”按钮。点击“关闭”按钮。
  171. 第二步:点击创建新主题的正文文本框或者回复文本框下面的“附加保存的公钥”按钮。
  172. 第三步:点击公钥上面的“使用以下公钥加密”按钮。在弹出的窗口中填入要加密的内容。点击“加密”按钮。点击“密文附加到回复并关闭弹窗”按钮。此步一般应该由他人完成。
  173. 第四步:刷新页面,使用自己发布的公钥加密的内容将自动解密。
  174. 注意1:多次回复带有同一链接会被 V2EX 判定为 spam。删除带链接的那行好像不影响加密解密。
  175. 注意2:公钥前面的那行不能删除。算是我的脚本的广告。
  176. 注意3PGP 相关的文章好像不多,推荐搜“非对称加密”了解相关知识。`);
  177. });
  178.  
  179. $('<button id="append_saved_pub_key" type="button"/>附加保存的公钥</button>').insertAfter("textarea#topic_content");
  180. $("#append_saved_pub_key").click(function(){
  181. $("textarea#topic_content").val($("textarea#topic_content").val() + markStr + "\r\n" + JSON.parse(localStorage.getItem("pub_key")));
  182. });
  183. //创建主题和创建回复的文本框好像不会同时出现。
  184. $('<button id="append_saved_pub_key" type="button"/>附加保存的公钥</button>').insertAfter("#reply_content");
  185. $("#append_saved_pub_key").click(function(){
  186. $("textarea#reply_content").val($("textarea#reply_content").val() + markStr + "\r\n" + JSON.parse(localStorage.getItem("pub_key")));
  187. });
  188. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址