{"id":478390,"date":"2026-05-03T14:46:13","date_gmt":"2026-05-03T14:46:13","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=478390"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=478390","title":{"rendered":"\u041a\u0430\u043a \u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043b E2EE-\u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440 \u043d\u0430 Spring Boot \u0438 WebCrypto \u2014 \u0438 \u043f\u043e\u0447\u0435\u043c\u0443 \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u0432\u0438\u0434\u0438\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440.<\/p>\n<p>\u042f Java-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0438 \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u044e \u0441 backend: Spring Boot, \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445, \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f, WebSocket \u2014 \u0432\u0441\u0451 \u0442\u043e, \u0447\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0437\u0430 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u043c.<\/p>\n<p>\u0412 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442 \u044f \u043f\u043e\u0439\u043c\u0430\u043b \u0441\u0435\u0431\u044f \u043d\u0430 \u043c\u044b\u0441\u043b\u0438: \u044f \u043a\u0430\u0436\u0434\u044b\u0439 \u0434\u0435\u043d\u044c \u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0441\u044c \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430\u043c\u0438, \u043d\u043e \u043f\u043b\u043e\u0445\u043e \u043f\u043e\u043d\u0438\u043c\u0430\u044e, \u043a\u0430\u043a \u043e\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u044b \u0432\u043d\u0443\u0442\u0440\u0438. \u041e\u043a\u0435\u0439, JWT, WebSocket, PostgreSQL, Redis \u2014 \u044d\u0442\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u043e. \u041d\u043e \u0447\u0442\u043e \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0444\u0440\u0430\u0437\u0430 \u201cend-to-end encryption\u201d? \u041a\u0430\u043a \u0441\u0435\u0440\u0432\u0435\u0440 \u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0435\u0441\u043b\u0438 \u043e\u043d \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0438\u0445 \u0447\u0438\u0442\u0430\u0442\u044c? \u0413\u0434\u0435 \u0436\u0438\u0432\u0443\u0442 \u043a\u043b\u044e\u0447\u0438? \u0427\u0442\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u0431\u0430\u0437\u0435? \u0427\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442, \u0435\u0441\u043b\u0438 \u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u0432\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430?<\/p>\n<p>\u0420\u0435\u0448\u0438\u043b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0443. \u041d\u0430\u043f\u0438\u0441\u0430\u043b \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440 \u0441 \u043d\u0443\u043b\u044f. \u041d\u0430\u0437\u0432\u0430\u043b Chaos Messenger.<\/p>\n<p>\u0421\u0440\u0430\u0437\u0443 \u0447\u0435\u0441\u0442\u043d\u043e: \u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u044f \u0438\u0437\u0443\u0447\u0430\u043b \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 Claude \u0438 ChatGPT \u2014 \u0447\u0438\u0442\u0430\u043b \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 X3DH \u0438 Double Ratchet, \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b \u043f\u0440\u0438\u043c\u0435\u0440\u044b, \u0437\u0430\u0434\u0430\u0432\u0430\u043b \u0432\u043e\u043f\u0440\u043e\u0441\u044b, \u043f\u043e\u043a\u0430 \u043d\u0435 \u0441\u043b\u043e\u0436\u0438\u043b\u0430\u0441\u044c \u0446\u0435\u043b\u044c\u043d\u0430\u044f \u043a\u0430\u0440\u0442\u0438\u043d\u0430. Frontend \u0442\u043e\u0436\u0435 \u0434\u0435\u043b\u0430\u043b\u0441\u044f \u0441 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u043e\u043c\u043e\u0449\u044c\u044e ChatGPT: \u044f backend-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, React \u0434\u043b\u044f \u043c\u0435\u043d\u044f \u043d\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0441\u0440\u0435\u0434\u0430. \u041d\u043e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430, backend, \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f WebCrypto, \u043c\u043e\u0434\u0435\u043b\u044c \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u043e\u0432, \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u2014 \u043c\u043e\u0438.<\/p>\n<p>\u0414\u043b\u044f \u043c\u0435\u043d\u044f AI \u0437\u0434\u0435\u0441\u044c \u0431\u044b\u043b \u043d\u0435 \u0437\u0430\u043c\u0435\u043d\u043e\u0439 \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f, \u0430 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u043c \u2014 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043a\u0430\u043a \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f, Stack Overflow \u0438 \u0440\u0435\u0432\u044c\u044e \u043a\u043e\u043b\u043b\u0435\u0433. \u0411\u0435\u0437 \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f threat model \u0438 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b \u0442\u0430\u043a\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043d\u0435 \u0441\u043e\u0431\u0440\u0430\u0442\u044c.<\/p>\n<p>\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 E2EE \u0438\u0437\u043d\u0443\u0442\u0440\u0438: \u043a\u0430\u043a \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u0435\u0441\u0441\u0438\u044f \u0447\u0435\u0440\u0435\u0437 X3DH, \u043a\u0430\u043a \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u0447\u0435\u0440\u0435\u0437 Symmetric Ratchet, \u043f\u043e\u0447\u0435\u043c\u0443 \u0441\u0435\u0440\u0432\u0435\u0440 \u0445\u0440\u0430\u043d\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u044b, \u0438 \u043a\u0430\u043a\u0438\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u044f \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043b \u043f\u043e \u0434\u043e\u0440\u043e\u0433\u0435.<\/p>\n<p>\u0421\u0442\u0435\u043a: Spring Boot 3, React 18, WebCrypto API, PostgreSQL, Redis, WebSocket\/STOMP, Prometheus, Grafana.<\/p>\n<h3>\u0412\u0430\u0436\u043d\u0430\u044f \u043e\u0433\u043e\u0432\u043e\u0440\u043a\u0430 \u043f\u0440\u043e web-E2EE<\/h3>\n<p>\u041a\u043e\u0433\u0434\u0430 \u044f \u0433\u043e\u0432\u043e\u0440\u044e, \u0447\u0442\u043e \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u044f \u0438\u043c\u0435\u044e \u0432 \u0432\u0438\u0434\u0443 backend, \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445, WebSocket-\u0441\u043b\u043e\u0439 \u0438 \u0443\u0436\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0435 ciphertext-\u043a\u043e\u043d\u0432\u0435\u0440\u0442\u044b. \u0423 \u043d\u0438\u0445 \u043d\u0435\u0442 \u043a\u043b\u044e\u0447\u0435\u0439 \u0438 plaintext.<\/p>\n<p>\u041d\u043e \u0443 web-E2EE \u0435\u0441\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430: frontend-\u043a\u043e\u0434 \u0442\u043e\u0436\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u0422\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0435\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0434\u0430\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0451\u043d\u043d\u044b\u0439 JavaScript, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043a\u0440\u0430\u0434\u0451\u0442 \u043a\u043b\u044e\u0447\u0438 \u0438\u043b\u0438 plaintext \u0434\u043e \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f. \u042d\u0442\u043e \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u043d\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u043c\u043e\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0430 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438 \u0432 \u0446\u0435\u043b\u043e\u043c.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u0430\u044f \u0444\u043e\u0440\u043c\u0443\u043b\u0438\u0440\u043e\u0432\u043a\u0430 \u0442\u0430\u043a\u0430\u044f: backend \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043a\u043b\u044e\u0447\u0438 \u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u0436\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u043b\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. \u0417\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u043f\u043e\u0434\u043c\u0435\u043d\u044b \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438: \u043f\u043e\u0434\u043f\u0438\u0441\u044c \u0441\u0431\u043e\u0440\u043e\u043a, \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u0430\u044f \u0432\u0435\u0440\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430, desktop\/mobile-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, reproducible builds.<\/p>\n<hr\/>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/940\/5b0\/9ce\/9405b09ce419024db0f7f160aae582ee.png\" width=\"2171\" height=\"724\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/940\/5b0\/9ce\/9405b09ce419024db0f7f160aae582ee.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/940\/5b0\/9ce\/9405b09ce419024db0f7f160aae582ee.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<hr\/>\n<h3>\u041f\u043e\u0447\u0435\u043c\u0443 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442<\/h3>\n<p>\u0411\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e &#171;\u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u043e\u0432&#187; \u043d\u0430 GitHub \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a:<\/p>\n<pre><code class=\"java\">message.setContent(request.getText());messageRepository.save(message);<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u0435\u0440\u0432\u0435\u0440 \u0437\u043d\u0430\u0435\u0442 \u0432\u0441\u0451. \u0412\u0438\u0434\u0438\u0442 \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435. \u0415\u0441\u043b\u0438 \u0411\u0414 \u0443\u0442\u0435\u043a\u043b\u0430 \u2014 \u0443\u0442\u0435\u043a\u043b\u0430 \u0432\u0441\u044f \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u043a\u0430. \u0415\u0441\u043b\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u0437\u043b\u043e\u043c\u0430\u043b\u0438 \u2014 \u0447\u0438\u0442\u0430\u0439 \u0447\u0442\u043e \u0445\u043e\u0447\u0435\u0448\u044c. \u0415\u0441\u043b\u0438 \u0437\u0430\u0432\u0442\u0440\u0430 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f \u0440\u0435\u0448\u0438\u0442 \u043f\u0440\u043e\u0434\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u2014 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043c\u0435\u0448\u0430\u0435\u0442.<\/p>\n<p>E2EE \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u043e \u0440\u0430\u0434\u0438\u043a\u0430\u043b\u044c\u043d\u043e: backend \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043a\u043b\u044e\u0447\u0438 \u0438 \u043d\u0435 \u0445\u0440\u0430\u043d\u0438\u0442 plaintext. \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0448\u0438\u0444\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u0435\u043b\u044f \u0434\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0432 \u0441\u0435\u0442\u044c, \u0430 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f.<\/p>\n<p>\u042d\u0442\u043e \u0443\u0436\u0435 \u043d\u0435 \u0432\u043e\u043f\u0440\u043e\u0441 \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u043a\u043e\u043d\u0444\u0438\u0434\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0432 \u0441\u0442\u0438\u043b\u0435 \u201c\u043c\u044b \u043e\u0431\u0435\u0449\u0430\u0435\u043c \u043d\u0435 \u0447\u0438\u0442\u0430\u0442\u044c\u201d. \u042d\u0442\u043e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435: \u0435\u0441\u043b\u0438 \u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0435\u0442 \u043a\u043b\u044e\u0447\u0430, \u043e\u043d \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044c ciphertext \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0432 \u0442\u0435\u043a\u0441\u0442.<\/p>\n<p>\u0417\u0432\u0443\u0447\u0438\u0442 \u043a\u0430\u043a \u043c\u0430\u0433\u0438\u044f. \u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u2014 \u0434\u0432\u0430 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 \u0438 \u043d\u0435\u043c\u043d\u043e\u0433\u043e WebCrypto.<\/p>\n<hr\/>\n<h3>\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f: \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u044b<\/h3>\n<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c \u0447\u0442\u043e \u0410\u043b\u0438\u0441\u0430 \u0445\u043e\u0447\u0435\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0411\u043e\u0431\u0443. \u0412\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u043f\u0438\u0441\u044c\u043c\u043e \u043d\u0430 \u0441\u0442\u043e\u043b \u0438 \u043d\u0430\u0434\u0435\u044f\u0442\u044c\u0441\u044f \u0447\u0442\u043e \u043d\u0438\u043a\u0442\u043e \u043d\u0435 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0435\u0442 \u2014 \u043e\u043d\u0430 \u043a\u043b\u0430\u0434\u0451\u0442 \u0435\u0433\u043e \u0432 \u0437\u0430\u043f\u0435\u0447\u0430\u0442\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043d\u0432\u0435\u0440\u0442. \u041a\u043e\u043d\u0432\u0435\u0440\u0442 \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0411\u043e\u0431 \u0441\u0432\u043e\u0438\u043c \u043a\u043b\u044e\u0447\u043e\u043c. \u0421\u0435\u0440\u0432\u0435\u0440 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442 \u043a\u043e\u043d\u0432\u0435\u0440\u0442 \u043d\u0435 \u0437\u0430\u0433\u043b\u044f\u0434\u044b\u0432\u0430\u044f \u0432\u043d\u0443\u0442\u0440\u044c.<\/p>\n<p>\u0418\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u043a\u043e\u0434\u0435. \u0412 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0443 \u043c\u0435\u043d\u044f \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"sql\">messages.content = '[encrypted]'  -- \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u0447\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0438message_envelopes.ciphertext = 'qzgHSg7zbwU6h8j8...'  -- \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043e AES-GCM<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u043e\u0433\u0434\u0430 \u044f \u0432\u043f\u0435\u0440\u0432\u044b\u0435 \u0443\u0432\u0438\u0434\u0435\u043b <code>[encrypted]<\/code> \u0432 \u0441\u0432\u043e\u0435\u0439 \u0411\u0414 \u0432\u043c\u0435\u0441\u0442\u043e \u0442\u0435\u043a\u0441\u0442\u0430 \u2014 \u0441\u0442\u0430\u043b\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0447\u0442\u043e \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e: \u0441\u0435\u0440\u0432\u0435\u0440 \u0441\u043e\u0437\u0434\u0430\u043b \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u0434\u043e\u0441\u0442\u0430\u0432\u0438\u043b \u0435\u0433\u043e, \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435, \u043d\u043e \u0442\u0430\u043a \u0438 \u043d\u0435 \u0443\u0437\u043d\u0430\u043b \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435.<\/p>\n<p>\u0410 \u0432\u043e\u0442 \u0447\u0442\u043e \u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043f\u0440\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0447\u0430\u0442\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 API:<\/p>\n<pre><code class=\"json\">{  \"chatId\": 32,  \"lastMessage\": \"[encrypted]\",  \"lastMessageAt\": \"2026-04-28T22:27:35.537016\"}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0435 <code>***<\/code>. \u041d\u0435 <code>[\u0441\u043a\u0440\u044b\u0442\u043e]<\/code>. \u0411\u0443\u043a\u0432\u0430\u043b\u044c\u043d\u043e <code>[encrypted]<\/code> \u2014 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0435\u0442 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430. \u041f\u043e\u0437\u0436\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043a\u0430\u043a\u043e\u0439 \u0431\u0430\u0433 \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u0432\u044b\u0442\u0435\u043a.<\/p>\n<hr\/>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c50\/03c\/a01\/c5003ca0128d96003f4330ace623562f.png\" width=\"831\" height=\"393\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/c50\/03c\/a01\/c5003ca0128d96003f4330ace623562f.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c50\/03c\/a01\/c5003ca0128d96003f4330ace623562f.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<hr\/>\n<h3>\u041e\u0442\u043a\u0443\u0434\u0430 \u0431\u0435\u0440\u0443\u0442\u0441\u044f \u043a\u043b\u044e\u0447\u0438: X3DH<\/h3>\n<p>\u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u0432\u043e\u043f\u0440\u043e\u0441: \u043a\u0430\u043a \u0410\u043b\u0438\u0441\u0430 \u0438 \u0411\u043e\u0431 \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442 \u043e\u0431\u0449\u0438\u0439 \u0441\u0435\u043a\u0440\u0435\u0442, \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u0440\u0430\u043d\u044c\u0448\u0435 \u043d\u0435 \u043e\u0431\u0449\u0430\u043b\u0438\u0441\u044c? \u0418 \u043a\u0430\u043a \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0441\u0435\u0440\u0432\u0435\u0440 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u043c\u043e\u0433 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u043d\u043e \u0441\u0430\u043c \u043d\u0435 \u0441\u043c\u043e\u0433 \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u043a\u043b\u044e\u0447?<\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f X3DH \u2014 Extended Triple Diffie-Hellman, \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0438\u0437 \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u044b Signal. \u0415\u0433\u043e \u0437\u0430\u0434\u0430\u0447\u0430 \u2014 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043e\u0431\u0449\u0438\u0439 \u0441\u0435\u043a\u0440\u0435\u0442 \u043c\u0435\u0436\u0434\u0443 \u0434\u0432\u0443\u043c\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0434\u043e\u043b\u0433\u043e\u0441\u0440\u043e\u0447\u043d\u044b\u0435 \u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438.<\/p>\n<h4>\u0427\u0442\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435<\/h4>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u043e\u043d \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 <strong>\u043f\u0430\u043a\u0435\u0442 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0445 \u043a\u043b\u044e\u0447\u0435\u0439<\/strong>:<\/p>\n<pre><code class=\"javascript\">\/\/ crypto-engine.js \u2014 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043a\u043b\u044e\u0447\u0435\u0439 \u043f\u0440\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430async function buildNewDeviceBundle() {    const identity     = await generateX25519KeyPair(); \/\/ \u0434\u043e\u043b\u0433\u043e\u0441\u0440\u043e\u0447\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430    const signedPreKey = await generateX25519KeyPair(); \/\/ \u0434\u043e\u043b\u0436\u0435\u043d \u0440\u043e\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438    const oneTimePreKeys = [];    for (let i = 0; i &lt; 50; i++) {        const kp = await generateX25519KeyPair();        oneTimePreKeys.push({            preKeyId: 1000 + i,            publicKey: await exportRawPublicKey(kp.publicKey),            privateKeyPkcs8: await exportPkcs8PrivateKey(kp.privateKey)        });    }    \/\/ ...}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0443\u0445\u043e\u0434\u044f\u0442 \u0442\u043e\u043b\u044c\u043a\u043e <strong>\u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435<\/strong> \u0447\u0430\u0441\u0442\u0438. \u041f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u044e\u0442\u0441\u044f \u0438 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u2014 \u0438 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u043f\u043e\u043a\u0438\u0434\u0430\u044e\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0432 \u0441\u0435\u0442\u044c.<\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u0432\u0430\u0436\u043d\u043e \u0441\u043a\u0430\u0437\u0430\u0442\u044c \u0447\u0435\u0441\u0442\u043d\u043e: \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0445 \u043a\u043b\u044e\u0447\u0435\u0439 \u0432 <code>localStorage<\/code> \u2014 \u044d\u0442\u043e \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0438\u0441\u0441, \u0430 \u043d\u0435 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u0430\u044f \u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c.<\/p>\n<p><code>localStorage<\/code> \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d JavaScript-\u043a\u043e\u0434\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u0415\u0441\u043b\u0438 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f XSS-\u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u044c \u0438\u043b\u0438 \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u043f\u043e\u0434\u043c\u0435\u043d\u0451\u043d\u043d\u044b\u0439 frontend-\u043a\u043e\u0434, \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0440\u0430\u0441\u0442\u044c. \u042d\u0442\u043e \u043d\u0435 \u043b\u043e\u043c\u0430\u0435\u0442 X3DH \u0438\u043b\u0438 AES-GCM, \u043d\u043e \u043b\u043e\u043c\u0430\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0443\u044e \u0441\u0440\u0435\u0434\u0443, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u044d\u0442\u0438 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f.<\/p>\n<p>\u0411\u043e\u043b\u0435\u0435 \u0441\u0442\u0440\u043e\u0433\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u2014 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Web Crypto API \u0441 <code>extractable: false<\/code>, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u0436\u0438\u043b \u0432\u043d\u0443\u0442\u0440\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u043e\u0433\u043e crypto runtime \u0438 \u0435\u0433\u043e \u043d\u0435\u043b\u044c\u0437\u044f \u0431\u044b\u043b\u043e \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u0431\u0430\u0439\u0442\u044b. \u041d\u043e \u0443 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u0435\u0441\u0442\u044c \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c: \u043a\u043b\u044e\u0447\u0438 \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430\u043c\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441 IndexedDB, \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u043e \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0438 \u043d\u0435 \u0441\u043b\u043e\u043c\u0430\u0442\u044c UX.<\/p>\n<p>\u0412 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u044b\u0445 E2EE-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u044b\u0431\u0438\u0440\u0430\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430\u043c\u0438:<\/p>\n<ol>\n<li>\n<p>\u0421\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u0432 <code>localStorage<\/code> \u0438\u043b\u0438 IndexedDB \u2014 \u043f\u0440\u043e\u0449\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c, \u043d\u043e \u043d\u0443\u0436\u043d\u043e \u043e\u0447\u0435\u043d\u044c \u0441\u0435\u0440\u044c\u0451\u0437\u043d\u043e \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u044c\u0441\u044f \u043a XSS \u0438 \u0446\u0435\u043b\u043e\u0441\u0442\u043d\u043e\u0441\u0442\u0438 frontend-\u043a\u043e\u0434\u0430.<\/p>\n<\/li>\n<li>\n<p><code>extractable: false<\/code> + IndexedDB \u2014 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0435\u0435, \u043d\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u0432 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0442\u0438\u0432\u043d\u043e\u0435 secure storage \u0432\u0440\u043e\u0434\u0435 Android Keystore \u0438\u043b\u0438 iOS Secure Enclave \u2014 \u043b\u0443\u0447\u0448\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0434\u043b\u044f \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432, \u043d\u043e \u043e\u043d \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u043e\u0431\u044b\u0447\u043d\u043e\u043c\u0443 web-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 Chaos Messenger \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442. \u042d\u0442\u043e \u043e\u0441\u043e\u0437\u043d\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0438\u0441\u0441 \u0434\u043b\u044f pet\/open-source \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0443\u0434\u043e\u0431\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435. \u041f\u0435\u0440\u0435\u0445\u043e\u0434 \u043d\u0430 non-extractable \u043a\u043b\u044e\u0447\u0438 \u0438 \u0431\u043e\u043b\u0435\u0435 \u0441\u0442\u0440\u043e\u0433\u0443\u044e \u043c\u043e\u0434\u0435\u043b\u044c \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0442\u043e\u0438\u0442 \u0432 roadmap.<\/p>\n<p>\u041a\u043b\u044e\u0447\u0435\u0432\u043e\u0439 \u043c\u043e\u043c\u0435\u043d\u0442: backend \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0435 ciphertext-\u043a\u043e\u043d\u0432\u0435\u0440\u0442\u044b. \u041d\u043e \u0437\u0430\u0449\u0438\u0442\u0430 \u043a\u043b\u044e\u0447\u0435\u0439 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430, \u0438 \u0435\u0451 \u043d\u0435\u043b\u044c\u0437\u044f \u0447\u0435\u0441\u0442\u043d\u043e \u0437\u0430\u043c\u0430\u043b\u0447\u0438\u0432\u0430\u0442\u044c.<\/p>\n<h4>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0441\u0435\u0441\u0441\u0438\u0438<\/h4>\n<p>\u041a\u043e\u0433\u0434\u0430 \u0410\u043b\u0438\u0441\u0430 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u043a\u0443 \u0441 \u0411\u043e\u0431\u043e\u043c \u0432\u043f\u0435\u0440\u0432\u044b\u0435, \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<pre><code class=\"javascript\">\/\/ crypto-engine.js \u2014 X3DH \u0441\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u0438\u043d\u0438\u0446\u0438\u0430\u0442\u043e\u0440\u0430async function createInitiatorSessionWrapped(localBundle, targetDevice) {    const identityPrivate       = await importPkcs8PrivateKey(localBundle.identity.privateKeyPkcs8);    const ephemeral             = await generateX25519KeyPair(); \/\/ \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u043b\u044e\u0447 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0441\u0435\u0441\u0441\u0438\u0438    const remoteIdentityPub     = await importRawPublicKey(targetDevice.identityPublicKey);    const remoteSignedPreKeyPub = await importRawPublicKey(targetDevice.signedPreKey.publicKey);    \/\/ X3DH \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e DH-\u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439.    \/\/ DH4 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0443 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f \u0435\u0441\u0442\u044c one-time prekey.    const dh1 = await derive32(identityPrivate,      remoteSignedPreKeyPub); \/\/ IK_alice \u00b7 SPK_bob    const dh2 = await derive32(ephemeral.privateKey, remoteIdentityPub);     \/\/ EK_alice \u00b7 IK_bob    const dh3 = await derive32(ephemeral.privateKey, remoteSignedPreKeyPub); \/\/ EK_alice \u00b7 SPK_bob    const parts = [dh1, dh2, dh3];    if (remoteOneTimePub) {        const dh4 = await derive32(ephemeral.privateKey, remoteOneTimePub);  \/\/ EK_alice \u00b7 OPK_bob        parts.push(dh4);    }    const combined = concat(...parts);    \/\/ \u0418\u0437 combined \u0447\u0435\u0440\u0435\u0437 HKDF \u0432\u044b\u0432\u043e\u0434\u0438\u043c rootKey \u0438 chainKey    const { rootKey, chainKey } = await deriveRootAndChainKey(combined);    \/\/ ...}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u043e\u043c X3DH \u0447\u0435\u0442\u0432\u0451\u0440\u0442\u0430\u044f DH-\u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u0441 one-time prekey \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u0430: \u043e\u043d\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u044b\u0434\u0430\u043b \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0439 OPK \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f. \u0412 \u043c\u043e\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u0442 \u043d\u0430\u0431\u043e\u0440 one-time prekeys \u043f\u0440\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0435\u0440\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431\u044b\u0447\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 DH4. \u0415\u0441\u043b\u0438 OPK \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u0438\u0441\u044c, \u0441\u0435\u0441\u0441\u0438\u044e \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043c\u043e\u0436\u043d\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 DH-\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b, \u043d\u043e \u044d\u0442\u043e \u0443\u0436\u0435 \u043c\u0435\u043d\u0435\u0435 \u0441\u0438\u043b\u044c\u043d\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442.<\/p>\n<p>\u0411\u043e\u0431, \u043f\u043e\u043b\u0443\u0447\u0438\u0432 \u043a\u043e\u043d\u0432\u0435\u0440\u0442 \u0441 \u044d\u0444\u0435\u043c\u0435\u0440\u043d\u044b\u043c \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c \u0410\u043b\u0438\u0441\u044b, \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u0442 \u0442\u0435 \u0436\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u043e \u0441\u0432\u043e\u0438\u043c\u0438 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u043c\u0438 \u043a\u043b\u044e\u0447\u0430\u043c\u0438 \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 <strong>\u0442\u043e\u0442 \u0436\u0435 \u0441\u0430\u043c\u044b\u0439<\/strong> <code>combined<\/code>. \u041c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u043a\u0430 \u0441\u0438\u043c\u043c\u0435\u0442\u0440\u0438\u0447\u043d\u0430.<\/p>\n<p>\u0421\u0435\u0440\u0432\u0435\u0440 \u0432 \u044d\u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442 \u0432\u0438\u0434\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u0438 \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043d\u0432\u0435\u0440\u0442. \u041e\u043d \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c \u043d\u0430\u0439\u0442\u0438 \u0434\u0440\u0443\u0433 \u0434\u0440\u0443\u0433\u0430, \u043d\u043e \u043d\u0435 \u0443\u0447\u0430\u0441\u0442\u0432\u0443\u0435\u0442 \u0432 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0438 \u0441\u0435\u043a\u0440\u0435\u0442\u0430.<\/p>\n<p>\u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c <code>combined<\/code> \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0445 \u043a\u043b\u044e\u0447\u0435\u0439 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e: \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c \u0437\u0434\u0435\u0441\u044c \u043e\u043f\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 Diffie-Hellman \u043d\u0430 Curve25519. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u0436\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0438 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c prekey bundle, \u043d\u043e \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u0442\u043e\u0442 \u0436\u0435 shared secret, \u0447\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.<\/p>\n<hr\/>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/0fd\/c40\/851\/0fdc408518df150fe284f062e7d7f31e.png\" width=\"2117\" height=\"743\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/0fd\/c40\/851\/0fdc408518df150fe284f062e7d7f31e.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/0fd\/c40\/851\/0fdc408518df150fe284f062e7d7f31e.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<hr\/>\n<h3>\u041a\u0430\u043a \u0448\u0438\u0444\u0440\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435: Symmetric Ratchet<\/h3>\n<p>X3DH \u0434\u0430\u0451\u0442 \u043d\u0430\u043c \u0441\u0442\u0430\u0440\u0442\u043e\u0432\u044b\u0439 <code>chainKey<\/code>. \u041d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043a\u043b\u044e\u0447 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u2014 \u043f\u043b\u043e\u0445\u0430\u044f \u0438\u0434\u0435\u044f. \u0415\u0441\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0434\u0438\u043d \u043a\u043b\u044e\u0447 \u0434\u043b\u044f \u0432\u0441\u0435\u0439 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u043a\u0438, \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0435\u0442\u0430\u0446\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430 \u0441\u0440\u0430\u0437\u0443 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0432\u0435\u0441\u044c \u043f\u043e\u0442\u043e\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439.<\/p>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 <strong>\u0441\u0438\u043c\u043c\u0435\u0442\u0440\u0438\u0447\u043d\u044b\u0439 ratchet<\/strong>. \u041f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0446\u0435\u043f\u043e\u0447\u043a\u0430 \u043a\u043b\u044e\u0447\u0435\u0439 \u043f\u0440\u043e\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u0432\u043f\u0435\u0440\u0451\u0434:<\/p>\n<pre><code class=\"javascript\">\/\/ crypto-engine.js \u2014 \u043e\u0434\u0438\u043d \u0448\u0430\u0433 \u0440atchetasync function ratchetStep(chainKeyBytes) {    const key = await crypto.subtle.importKey(        'raw', chainKeyBytes,        { name: 'HMAC', hash: 'SHA-256' },        false, ['sign']    );    \/\/ messageKey \u2014 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u0434\u043b\u044f \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f    const mkBits = await crypto.subtle.sign('HMAC', key, new Uint8Array([0x01]));    \/\/ nextChainKey \u2014 \u0441\u0442\u0430\u0440\u0442\u043e\u0432\u044b\u0439 \u043a\u043b\u044e\u0447 \u0434\u043b\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f    const ckBits = await crypto.subtle.sign('HMAC', key, new Uint8Array([0x02]));    const messageKey = await crypto.subtle.importKey(        'raw', new Uint8Array(mkBits),        { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']    );    return { messageKey, nextChainKey: new Uint8Array(ckBits) };}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412\u0438\u0437\u0443\u0430\u043b\u044c\u043d\u043e \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code>chainKey\u2080 \u2500\u2500HMAC(\u00b7,0x02)\u2500\u2500\u25ba chainKey\u2081 \u2500\u2500HMAC(\u00b7,0x02)\u2500\u2500\u25ba chainKey\u2082 \u2500\u2500\u25ba    \u2502                            \u2502                            \u2502 HMAC(\u00b7,0x01)               HMAC(\u00b7,0x01)               HMAC(\u00b7,0x01)    \u2502                            \u2502                            \u2502    \u25bc                            \u25bc                            \u25bcmessageKey\u2081                 messageKey\u2082                 messageKey\u2083(AES-GCM msg #1)            (AES-GCM msg #2)            (AES-GCM msg #3)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><code>messageKey<\/code> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0434\u043d\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0447\u0435\u0440\u0435\u0437 AES-GCM, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e <strong>\u0443\u043d\u0438\u0447\u0442\u043e\u0436\u0430\u0435\u0442\u0441\u044f<\/strong>. \u0415\u0441\u043b\u0438 \u0430\u0442\u0430\u043a\u0443\u044e\u0449\u0438\u0439 \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0435\u0442\u0438\u0440\u0443\u0435\u0442 <code>messageKey\u2082<\/code> \u2014 \u043e\u043d \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0442\u043e\u0440\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435. <code>chainKey\u2080<\/code> \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u0438\u0437 \u043d\u0435\u0433\u043e \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u2014 HMAC-SHA256 \u043d\u0435\u043e\u0431\u0440\u0430\u0442\u0438\u043c.<\/p>\n<p>\u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u0442\u0430\u043a\u043e\u0439 \u0441\u0438\u043c\u043c\u0435\u0442\u0440\u0438\u0447\u043d\u043e\u0439 \u0446\u0435\u043f\u043e\u0447\u043a\u0438 \u044d\u0442\u043e \u0434\u0430\u0451\u0442 forward secrecy \u043d\u0430\u0437\u0430\u0434 \u043f\u043e \u0446\u0435\u043f\u043e\u0447\u043a\u0435: \u0437\u043d\u0430\u044f \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0438\u043b\u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 messageKey, \u043d\u0435\u043b\u044c\u0437\u044f \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0442\u0430\u0440\u044b\u0435 \u043a\u043b\u044e\u0447\u0438. \u041d\u043e \u044d\u0442\u043e \u0435\u0449\u0451 \u043d\u0435 \u043f\u043e\u043b\u043d\u044b\u0439 Double Ratchet \u2014 \u043e\u0431 \u044d\u0442\u043e\u043c \u043d\u0438\u0436\u0435.<\/p>\n<hr\/>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c82\/8e6\/def\/c828e6def51eeb378c63cc173b83183b.png\" width=\"2048\" height=\"768\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/c82\/8e6\/def\/c828e6def51eeb378c63cc173b83183b.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c82\/8e6\/def\/c828e6def51eeb378c63cc173b83183b.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<hr\/>\n<p>\u0421\u0430\u043c\u043e \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"javascript\">async function encryptWithRatchet(session, plainText) {    const chainKeyBytes = b64ToBytes(session.sendingChainKey);    const { messageKey, nextChainKey } = await ratchetStep(chainKeyBytes);    \/\/ \u041f\u0440\u043e\u0434\u0432\u0438\u0433\u0430\u0435\u043c \u0446\u0435\u043f\u043e\u0447\u043a\u0443 \u0432\u043f\u0435\u0440\u0451\u0434 \u2014 \u0441\u0442\u0430\u0440\u044b\u0439 chainKey \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f    session.sendingChainKey = bytesToB64(nextChainKey);    session.sendingIndex++;    \/\/ \u0428\u0438\u0444\u0440\u0443\u0435\u043c AES-GCM \u0441 \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u043c nonce    const encrypted = await aesEncryptWithKey(plainText, messageKey);    return { encrypted, messageIndex: session.sendingIndex - 1 };}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0410 \u0432\u043e\u0442 \u0447\u0442\u043e \u0443\u0445\u043e\u0434\u0438\u0442 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u2014 \u0436\u0438\u0432\u043e\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0437 DevTools:<\/p>\n<pre><code class=\"json\">{  \"envelope\": {    \"ciphertext\": \"qzgHSg7zbwU6h8j8RqCPUYBWHJLi78eR9C0tj9I=\",    \"nonce\": \"6KPcVjbpM4FUB0Vz\",    \"senderIdentityPublicKey\": \"B4pERe0xKmSdiQPR+kLWWmI0nloC8Za3RBTg+occHF0=\",    \"targetDeviceId\": \"device-2aa3ae0e-ee08-4261-aa09-7d8f800b61e9\",    \"messageType\": \"PREKEY_WHISPER\",    \"messageIndex\": 0  }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0421\u0435\u0440\u0432\u0435\u0440 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 <code>ciphertext<\/code> \u0438 <code>nonce<\/code>. \u0420\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u0442\u044c \u0431\u0435\u0437 <code>messageKey<\/code> \u2014 \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e.<\/p>\n<hr\/>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b60\/12d\/a5d\/b6012da5d6a18472fafcdfba9658c1a8.png\" width=\"944\" height=\"514\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/b60\/12d\/a5d\/b6012da5d6a18472fafcdfba9658c1a8.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b60\/12d\/a5d\/b6012da5d6a18472fafcdfba9658c1a8.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<hr\/>\n<h3>\u0412\u0430\u0436\u043d\u0430\u044f \u043e\u0433\u043e\u0432\u043e\u0440\u043a\u0430: \u044d\u0442\u043e \u0435\u0449\u0451 \u043d\u0435 \u043f\u043e\u043b\u043d\u044b\u0439 Double Ratchet<\/h3>\n<p>\u0412 \u044d\u0442\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d Symmetric Ratchet \u2014 \u0446\u0435\u043f\u043e\u0447\u043a\u0430, \u0433\u0434\u0435 \u0438\u0437 <code>chainKey<\/code> \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0432\u044b\u0432\u043e\u0434\u0438\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 <code>messageKey<\/code>, \u0430 \u0441\u0430\u043c\u0430 \u0446\u0435\u043f\u043e\u0447\u043a\u0430 \u043f\u0440\u043e\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u0432\u043f\u0435\u0440\u0451\u0434.<\/p>\n<p>\u042d\u0442\u043e \u0437\u0430\u0449\u0438\u0449\u0430\u0435\u0442 \u043f\u0440\u043e\u0448\u043b\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f: \u0435\u0441\u043b\u0438 \u0430\u0442\u0430\u043a\u0443\u044e\u0449\u0438\u0439 \u0443\u0437\u043d\u0430\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043a\u043b\u044e\u0447 \u0438\u043b\u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 <code>messageKey<\/code>, \u043e\u043d \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u043a\u0430\u0442\u0438\u0442\u044c HMAC \u043d\u0430\u0437\u0430\u0434 \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u0442\u0430\u0440\u044b\u0435 \u043a\u043b\u044e\u0447\u0438.<\/p>\n<p>\u041d\u043e \u044d\u0442\u043e \u043d\u0435 \u043f\u043e\u043b\u043d\u044b\u0439 Double Ratchet \u0438\u0437 Signal Protocol.<\/p>\n<p>\u0412 \u043f\u043e\u043b\u043d\u043e\u043c Double Ratchet \u0435\u0441\u0442\u044c \u0435\u0449\u0451 DH ratchet step: \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442 \u043d\u043e\u0432\u044b\u0439 Diffie-Hellman \u043e\u0431\u043c\u0435\u043d \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u044e\u0442 root key. \u042d\u0442\u043e \u0434\u0430\u0451\u0442 break-in recovery \u2014 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c \u0431\u0443\u0434\u0443\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043f\u043e\u0441\u043b\u0435 \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0435\u0442\u0430\u0446\u0438\u0438 \u0447\u0430\u0441\u0442\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f.<\/p>\n<p>\u0412 \u043c\u043e\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 DH ratchet step \u043f\u043e\u043a\u0430 \u043d\u0435\u0442. \u0415\u0441\u043b\u0438 \u0430\u0442\u0430\u043a\u0443\u044e\u0449\u0438\u0439 \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0441\u0435\u0441\u0441\u0438\u0438 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u0438 \u0441\u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0442\u044c \u0435\u0433\u043e \u0447\u0438\u0442\u0430\u0442\u044c, \u043e\u043d \u0441\u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0431\u0443\u0434\u0443\u0449\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0434\u043e \u043f\u0435\u0440\u0435\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0441\u0435\u0441\u0441\u0438\u0438. \u042d\u0442\u043e \u0447\u0435\u0441\u0442\u043d\u043e\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438, \u0438 \u043e\u043d\u043e \u0441\u0442\u043e\u0438\u0442 \u043f\u0435\u0440\u0432\u044b\u043c \u043f\u0443\u043d\u043a\u0442\u043e\u043c \u0432 roadmap.<\/p>\n<hr\/>\n<h3>\u041c\u0443\u043b\u044c\u0442\u0438\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e: \u043e\u0434\u0438\u043d \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c, \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u043e\u0432<\/h3>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u043d\u0435\u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442: \u0432 E2EE \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0430\u0434\u0440\u0435\u0441\u0443\u0435\u0442\u0441\u044f \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e, \u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0443 \u0411\u043e\u0431\u0430 \u0434\u0432\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u2014 \u0442\u0435\u043b\u0435\u0444\u043e\u043d \u0438 \u043d\u043e\u0443\u0442\u0431\u0443\u043a \u2014 \u043d\u0443\u0436\u0435\u043d \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 encrypted envelope \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u0421\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0432\u0437\u044f\u0442\u044c \u043e\u0434\u0438\u043d \u043a\u043e\u043d\u0432\u0435\u0440\u0442, \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0438 \u201c\u043f\u0435\u0440\u0435\u0443\u043f\u0430\u043a\u043e\u0432\u0430\u0442\u044c\u201d \u0434\u043b\u044f \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430: \u0443 \u043d\u0435\u0433\u043e \u043d\u0435\u0442 \u043a\u043b\u044e\u0447\u0435\u0439 \u0438 \u043e\u043d \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 plaintext.<\/p>\n<p>\u0417\u043d\u0430\u0447\u0438\u0442 \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043d\u0443\u0436\u043d\u043e \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0430 \u0447\u0430\u0442\u0430.<\/p>\n<pre><code class=\"javascript\">\/\/ crypto-engine.js \u2014 fanout \u043d\u0430 \u0432\u0441\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430async function buildFanoutRequest(api, chatId, plainText) {    const localBundle = await ensureDeviceRegistered(api);    \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0432\u0441\u0435\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0432\u0441\u0435\u0445 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432 \u0447\u0430\u0442\u0430    const resolved = await api('\/api\/crypto\/resolve-chat-devices\/' + chatId, { method: 'POST' });    const envelopes = [];    for (const targetDevice of resolved.targetDevices) {        \/\/ \u0414\u043b\u044f \u0441\u0432\u043e\u0435\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u2014 \u043e\u0441\u043e\u0431\u043e\u0435 \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435 (SELF_WHISPER)        if (targetDevice.deviceId === localBundle.deviceId) {            const encrypted = await encryptSelfEnvelope(localBundle, plainText);            envelopes.push({ ...encrypted, messageType: 'SELF_WHISPER' });            continue;        }        \/\/ \u0414\u043b\u044f \u0447\u0443\u0436\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u2014 X3DH + Ratchet        let session = getSession(localBundle.deviceId, targetDevice.deviceId);        let ephemeralPublicKey = null;        if (!session) {            \/\/ \u041f\u0435\u0440\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u2014 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c X3DH \u0441\u0435\u0441\u0441\u0438\u044e            const created = await createInitiatorSessionWrapped(localBundle, targetDevice);            session = created.session;            ephemeralPublicKey = created.ephemeralPublicKey;        }        const { encrypted, messageIndex } = await encryptWithRatchet(session, plainText);        storeSession(localBundle.deviceId, targetDevice.deviceId, session);        envelopes.push({            targetDeviceId: targetDevice.deviceId,            ciphertext: encrypted.ciphertext,            nonce: encrypted.nonce,            messageIndex,            ephemeralPublicKey,  \/\/ null \u0435\u0441\u043b\u0438 \u0441\u0435\u0441\u0441\u0438\u044f \u0443\u0436\u0435 \u0431\u044b\u043b\u0430            messageType: ephemeralPublicKey ? 'PREKEY_WHISPER' : 'WHISPER'        });    }    return { chatId, envelopes };}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0414\u043b\u044f \u0447\u0430\u0442\u0430 \u0433\u0434\u0435 \u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043f\u043e 2 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u2014 4 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430 \u043d\u0430 \u043e\u0434\u043d\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435. \u0414\u043b\u044f \u0433\u0440\u0443\u043f\u043f\u044b \u0438\u0437 10 \u0447\u0435\u043b\u043e\u0432\u0435\u043a \u2014 \u043f\u043e\u0442\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e 20 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u043e\u0432. \u042d\u0442\u043e \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e, \u044d\u0442\u043e \u0446\u0435\u043d\u0430 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438.<\/p>\n<hr\/>\n<h3>\u0421\u0435\u0440\u0432\u0435\u0440: \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u043e\u0432<\/h3>\n<p>\u041d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0441\u044f \u0441 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u043e\u043c <code>[encrypted]<\/code>, \u0430 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e:<\/p>\n<pre><code class=\"java\">\/\/ MessageService.javamessage.setContent(\"[encrypted]\"); \/\/ \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u0447\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0438messageRepository.save(message);\/\/ \u041a\u0430\u0436\u0434\u044b\u0439 \u043a\u043e\u043d\u0432\u0435\u0440\u0442 \u2014 \u0434\u043b\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430Map&lt;String, MessageEnvelope&gt; byDevice = persistEnvelopes(message, sender, request.getEnvelopes());<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u2014 fanout \u043f\u043e WebSocket. \u041a\u0430\u0436\u0434\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 <strong>\u0441\u0432\u043e\u0439<\/strong> \u043a\u043e\u043d\u0432\u0435\u0440\u0442 \u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0433\u043e:<\/p>\n<pre><code class=\"java\">\/\/ MessageService.java \u2014 per-device \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430private void fanoutCreatedEvent(Message message, Map&lt;String, MessageEnvelope&gt; byDevice) {    byDevice.forEach((deviceId, envelope) -&gt;        messagingTemplate.convertAndSend(            \"\/topic\/devices\/\" + deviceId + \"\/chats\/\" + message.getChatId(),            toDeviceEvent(\"MESSAGE_CREATED\", message, envelope, envelope.getTargetUserId())        )    );}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u0432\u0430\u0436\u043d\u043e\u0435 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e WebSocket-\u0447\u0430\u0442\u0430. \u0412 \u043e\u0431\u044b\u0447\u043d\u043e\u043c \u0447\u0430\u0442\u0435 \u0441\u0435\u0440\u0432\u0435\u0440 \u0440\u0430\u0441\u0441\u044b\u043b\u0430\u0435\u0442 \u043e\u0434\u043d\u043e \u0438 \u0442\u043e \u0436\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0432\u0441\u0435\u043c \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0430\u043c. \u0412 E2EE-\u0447\u0430\u0442\u0435 \u0441\u0435\u0440\u0432\u0435\u0440 \u0440\u0430\u0441\u0441\u044b\u043b\u0430\u0435\u0442 \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0440\u0430\u0437\u043d\u044b\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c: payload \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u0432\u043e\u0439 ciphertext, \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u0434 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u0441\u0435\u0441\u0441\u0438\u044e.<\/p>\n<p>\u0422\u043e\u043f\u0438\u043a <code>\/topic\/devices\/{deviceId}\/chats\/{chatId}<\/code> \u2014 \u0441\u0442\u0440\u043e\u0433\u043e \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0439. \u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0410 \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043a\u043e\u043d\u0432\u0435\u0440\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0411. \u041d\u0438\u043a\u0430\u043a\u043e\u0433\u043e broadcast \u2014 \u0442\u043e\u043b\u044c\u043a\u043e \u0430\u0434\u0440\u0435\u0441\u043d\u0430\u044f \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430.<\/p>\n<hr\/>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0446\u0435\u043b\u0438\u043a\u043e\u043c<\/h3>\n<pre><code>\u0411\u0440\u0430\u0443\u0437\u0435\u0440\u251c\u2500\u2500 React 18 + Vite\u251c\u2500\u2500 crypto-engine.js        \u2190 X3DH \u00b7 Symmetric Ratchet \u00b7 AES-GCM \u00b7 WebCrypto\u251c\u2500\u2500 local device bundle     \u2190 identity key \u00b7 signed prekey \u00b7 one-time prekeys\u251c\u2500\u2500 REST \/api\/*             \u2190 auth \u00b7 profile \u00b7 chats \u00b7 devices \u00b7 prekeys\u2514\u2500\u2500 WebSocket \/ws           \u2190 per-device STOMP topicsSpring Boot Backend\u251c\u2500\u2500 auth\/                   \u2190 phone OTP \u00b7 email \u00b7 JWT \u00b7 refresh tokens\u251c\u2500\u2500 crypto\/                 \u2190 device registry \u00b7 prekey bundles \u00b7 envelope fanout\u251c\u2500\u2500 chat\/                   \u2190 chats \u00b7 participants \u00b7 message metadata\u251c\u2500\u2500 message\/                \u2190 encrypted envelopes \u00b7 receipts \u00b7 events\u251c\u2500\u2500 infra\/ws\/               \u2190 WebSocket \u00b7 JWT auth \u00b7 device routing\u2514\u2500\u2500 infra\/presence\/         \u2190 online status \u00b7 typingPostgreSQL\u2514\u2500\u2500 users \u00b7 devices \u00b7 chats \u00b7 messages([encrypted]) \u00b7 envelopes(ciphertext, nonce)Redis\u2514\u2500\u2500 refresh tokens \u00b7 online presence \u00b7 SMS rate limitsObservability\u2514\u2500\u2500 Actuator \u00b7 Prometheus \u00b7 Grafana<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<hr\/>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/01f\/354\/798\/01f35479860368b2fb347a15bd4b5f17.png\" width=\"1942\" height=\"809\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/01f\/354\/798\/01f35479860368b2fb347a15bd4b5f17.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/01f\/354\/798\/01f35479860368b2fb347a15bd4b5f17.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<hr\/>\n<h3>\u0411\u0430\u0433 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043e\u043b\u0433\u043e \u043d\u0435 \u0437\u0430\u043c\u0435\u0447\u0430\u043b<\/h3>\n<p>\u0412 \u043f\u0430\u043d\u0435\u043b\u0438 \u0447\u0430\u0442\u043e\u0432 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0435\u0432\u044c\u044e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. \u042f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u044d\u0442\u043e \u0447\u0435\u0440\u0435\u0437 <code>ChatService.getMyChats()<\/code> \u2014 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044e \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438\u0437 \u0411\u0414 \u0438 \u043e\u0442\u0434\u0430\u044e \u043a\u043b\u0438\u0435\u043d\u0442\u0443.<\/p>\n<p>\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u044e \u2014 \u0432 \u0441\u043f\u0438\u0441\u043a\u0435 \u0447\u0430\u0442\u043e\u0432 \u0443 \u0432\u0441\u0435\u0445 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043e <code>[encrypted]<\/code>.<\/p>\n<p>\u041a\u043e\u043d\u0435\u0447\u043d\u043e. \u0421\u0435\u0440\u0432\u0435\u0440 \u0436\u0435 \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u0447\u0442\u043e \u0442\u0430\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043e.<\/p>\n<p>\u042f \u043f\u043e\u043b\u0447\u0430\u0441\u0430 \u0434\u0443\u043c\u0430\u043b \u043a\u0430\u043a \u0440\u0435\u0448\u0438\u0442\u044c \u044d\u0442\u043e \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435. \u041f\u043e\u0442\u043e\u043c \u0434\u043e\u0448\u043b\u043e: <strong>\u043d\u0435\u043b\u044c\u0437\u044f \u0440\u0435\u0448\u0438\u0442\u044c \u044d\u0442\u043e \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u2014 \u0443 \u043d\u0435\u0433\u043e \u043d\u0435\u0442 \u043a\u043b\u044e\u0447\u0435\u0439<\/strong>. \u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435.<\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043e\u0442\u043a\u0440\u044b\u043b \u0447\u0430\u0442 \u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u2014 \u043a\u0435\u0448\u0438\u0440\u0443\u0435\u043c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438:<\/p>\n<pre><code class=\"javascript\">\/\/ \u041f\u043e\u0441\u043b\u0435 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0432 useMessages.jspreviewCache.set(chatId, decryptedText.slice(0, 60));\/\/ \u0412 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0435 ChatList \u2014 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043a\u0435\u0448const preview = previewCache.get(chatId) ?? '\ud83d\udd12 \u0417\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043e';<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u042d\u0442\u043e \u0445\u043e\u0440\u043e\u0448\u0438\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a E2EE \u043c\u0435\u043d\u044f\u0435\u0442 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u043e\u0435 \u043c\u044b\u0448\u043b\u0435\u043d\u0438\u0435 backend-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430. \u0412 \u043e\u0431\u044b\u0447\u043d\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 preview \u2014 \u044d\u0442\u043e \u043f\u043e\u043b\u0435 \u0432 SQL-\u0437\u0430\u043f\u0440\u043e\u0441\u0435. \u0412 E2EE-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 preview \u2014 \u044d\u0442\u043e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0435 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043b\u0438\u0435\u043d\u0442 \u0432\u0438\u0434\u0435\u043b plaintext.<\/p>\n<p>\u041f\u0440\u043e\u0441\u0442\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435. \u041d\u043e \u0447\u0442\u043e\u0431\u044b \u043a \u043d\u0435\u043c\u0443 \u043f\u0440\u0438\u0439\u0442\u0438 \u043d\u0443\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u0440\u0438\u043d\u044f\u0442\u044c \u0438\u0434\u0435\u044e \u0447\u0442\u043e \u0441\u0435\u0440\u0432\u0435\u0440 \u0437\u0434\u0435\u0441\u044c \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435 \u043f\u0440\u0438 \u0434\u0435\u043b\u0430\u0445 \u2014 \u0438 \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u0442\u044c \u043f\u044b\u0442\u0430\u0442\u044c\u0441\u044f \u0440\u0435\u0448\u0438\u0442\u044c \u0437\u0430\u0434\u0430\u0447\u0443 \u043d\u0430 \u0435\u0433\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u0435.<\/p>\n<hr\/>\n<h3>Rate limiting: \u0434\u044b\u0440\u0430 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043b\u0435\u0433\u043a\u043e \u043d\u0435 \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c<\/h3>\n<p>\u042d\u043d\u0434\u043f\u043e\u0438\u043d\u0442 <code>\/api\/auth\/send-code<\/code> \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 SMS \u0441 \u043a\u043e\u0434\u043e\u043c. \u0411\u0435\u0437 \u0437\u0430\u0449\u0438\u0442\u044b \u043b\u044e\u0431\u043e\u0439 \u0441\u043a\u0440\u0438\u043f\u0442 \u043c\u043e\u0436\u0435\u0442 \u0434\u0451\u0440\u0433\u0430\u0442\u044c \u0435\u0433\u043e \u0442\u044b\u0441\u044f\u0447\u0438 \u0440\u0430\u0437 \u2014 \u044d\u0442\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f SMS pumping fraud, SMS \u0441\u0442\u043e\u044f\u0442 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u0434\u0435\u043d\u0435\u0433.<\/p>\n<p>Redis \u0443 \u043d\u0430\u0441 \u0443\u0436\u0435 \u0431\u044b\u043b \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043e\u043d\u043b\u0430\u0439\u043d-\u0441\u0442\u0430\u0442\u0443\u0441\u043e\u0432. \u0414\u043e\u0431\u0430\u0432\u0438\u043b rate limiting \u043f\u043e\u0432\u0435\u0440\u0445 \u043d\u0435\u0433\u043e:<\/p>\n<pre><code class=\"java\">\/\/ SmsRateLimiter.javapublic void checkAndIncrement(String phone) {    \/\/ \u041d\u0435 \u0431\u043e\u043b\u0435\u0435 3 SMS \u0437\u0430 10 \u043c\u0438\u043d\u0443\u0442    checkLimit(\"sms:rate:short:\" + phone, 3, Duration.ofMinutes(10));    \/\/ \u041d\u0435 \u0431\u043e\u043b\u0435\u0435 10 SMS \u0437\u0430 24 \u0447\u0430\u0441\u0430    checkLimit(\"sms:rate:day:\"   + phone, 10, Duration.ofHours(24));}private void checkLimit(String key, int maxAttempts, Duration window) {    Long count = redisTemplate.opsForValue().increment(key);    if (count == 1) {        redisTemplate.expire(key, window);    }    if (count &gt; maxAttempts) {        long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);        throw new RateLimitException(\"Too many requests\", ttl);    }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0440\u0438 \u043f\u0440\u0435\u0432\u044b\u0448\u0435\u043d\u0438\u0438 \u2014 HTTP 429 \u0441 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u043c <code>Retry-After<\/code>. \u041a\u043b\u0438\u0435\u043d\u0442 \u0437\u043d\u0430\u0435\u0442 \u0447\u0435\u0440\u0435\u0437 \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0435\u043a\u0443\u043d\u0434 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c.<\/p>\n<p>\u0412\u0430\u0436\u043d\u044b\u0439 \u043d\u044e\u0430\u043d\u0441: \u0432 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438, \u0435\u0441\u043b\u0438 Redis \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d, \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0435 \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e. \u0414\u043b\u044f pet-\u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u044d\u0442\u043e \u043f\u0440\u0438\u0435\u043c\u043b\u0435\u043c\u044b\u0439 \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0438\u0441\u0441: \u043b\u0443\u0447\u0448\u0435 \u0440\u0438\u0441\u043a\u043d\u0443\u0442\u044c \u043e\u0434\u043d\u0438\u043c \u043b\u0438\u0448\u043d\u0438\u043c SMS, \u0447\u0435\u043c \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u0432\u0445\u043e\u0434 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435.<\/p>\n<p>\u0412 production \u044f \u0431\u044b \u0441\u0434\u0435\u043b\u0430\u043b \u0441\u0442\u0440\u043e\u0436\u0435: fallback in-memory \u043b\u0438\u043c\u0438\u0442 \u043d\u0430 \u0438\u043d\u0441\u0442\u0430\u043d\u0441, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043b\u0438\u043c\u0438\u0442\u044b \u043f\u043e IP \u0438 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0443, \u0430\u043d\u0442\u0438\u0444\u0440\u043e\u0434-\u043b\u043e\u0433\u0438\u043a\u0443 \u0438 \u0430\u043b\u0435\u0440\u0442\u044b \u043d\u0430 \u0432\u0441\u043f\u043b\u0435\u0441\u043a\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043a\u043e\u0434\u043e\u0432.<\/p>\n<hr\/>\n<h3>\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f WebSocket<\/h3>\n<p>\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u2014 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f WebSocket \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439. HTTP-\u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u044b Spring Security \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u043d\u043e WebSocket \u2014 \u0434\u0440\u0443\u0433\u043e\u0435 \u0434\u0435\u043b\u043e. STOMP-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0434\u0438\u043d \u0440\u0430\u0437, \u0438 \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c JWT \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438.<\/p>\n<pre><code class=\"java\">\/\/ WebSocketAuthChannelInterceptor.java@Overridepublic Message&lt;?&gt; preSend(Message&lt;?&gt; message, MessageChannel channel) {    StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);    if (StompCommand.CONNECT.equals(accessor.getCommand())) {        String token = accessor.getFirstNativeHeader(\"Authorization\");        if (token == null || !token.startsWith(\"Bearer \")) {            throw new AuthException(\"Missing WebSocket auth token\");        }        \/\/ \u0412\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c JWT \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c principal        Authentication auth = jwtAuthProvider.authenticate(token.substring(7));        accessor.setUser(auth);    }    return message;}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0432\u0430\u0436\u043d\u043e \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c JWT, \u043d\u043e \u0438 \u0441\u0432\u044f\u0437\u0430\u0442\u044c WebSocket-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u0434\u0438\u043d, \u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0443 \u043d\u0435\u0433\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e, \u0430 encrypted envelope \u0430\u0434\u0440\u0435\u0441\u043e\u0432\u0430\u043d \u0438\u043c\u0435\u043d\u043d\u043e deviceId.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u044f \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u043e\u043a\u0435\u043d, \u043d\u043e \u0438 X-Device-Id: \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u0438 \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0435\u043c\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e. \u0418\u043d\u0430\u0447\u0435 \u043b\u0435\u0433\u043a\u043e \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044c per-device E2EE-\u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0443 \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0432 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 broadcast \u043f\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e.<\/p>\n<hr\/>\n<h3>\u0427\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u2014 \u0436\u0438\u0432\u044b\u0435 \u0441\u043a\u0440\u0438\u043d\u044b<\/h3>\n<hr\/>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/594\/f8b\/89e\/594f8b89e9d9e263a3c146fe169f817b.png\" width=\"1884\" height=\"835\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/594\/f8b\/89e\/594f8b89e9d9e263a3c146fe169f817b.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/594\/f8b\/89e\/594f8b89e9d9e263a3c146fe169f817b.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<hr\/>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/76f\/c84\/399\/76fc84399b8c0271d6c74087d2286196.png\" width=\"2170\" height=\"725\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/76f\/c84\/399\/76fc84399b8c0271d6c74087d2286196.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/76f\/c84\/399\/76fc84399b8c0271d6c74087d2286196.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<hr\/>\n<p>\u0427\u0442\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043e:<\/p>\n<ul>\n<li>\n<p>E2EE-\u043c\u043e\u0434\u0435\u043b\u044c \u0441 per-device encrypted envelopes<\/p>\n<\/li>\n<li>\n<p>X3DH session setup + Symmetric Ratchet + AES-GCM<\/p>\n<\/li>\n<li>\n<p>\u041c\u0443\u043b\u044c\u0442\u0438\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e<\/p>\n<\/li>\n<li>\n<p>\u041b\u0438\u0447\u043d\u044b\u0435 \u0438 \u0433\u0440\u0443\u043f\u043f\u043e\u0432\u044b\u0435 \u0447\u0430\u0442\u044b<\/p>\n<\/li>\n<li>\n<p>Realtime \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u0447\u0435\u0440\u0435\u0437 WebSocket\/STOMP<\/p>\n<\/li>\n<li>\n<p>\u0421\u0442\u0430\u0442\u0443\u0441\u044b SENT \u2192 DELIVERED \u2192 READ<\/p>\n<\/li>\n<li>\n<p>\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 soft delete \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/p>\n<\/li>\n<li>\n<p>Online presence, typing indicator<\/p>\n<\/li>\n<li>\n<p>\u0424\u043e\u0442\u043e-\u0432\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0438\u0441\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439<\/p>\n<\/li>\n<li>\n<p>Rate limiting \u043d\u0430 SMS \u0447\u0435\u0440\u0435\u0437 Redis<\/p>\n<\/li>\n<li>\n<p>Prometheus \u043c\u0435\u0442\u0440\u0438\u043a\u0438 + Grafana \u0434\u0430\u0448\u0431\u043e\u0440\u0434<\/p>\n<\/li>\n<li>\n<p>Swagger UI \u0441 JWT \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439<\/p>\n<\/li>\n<li>\n<p>24 backend-\u0442\u0435\u0441\u0442\u0430 \u043d\u0430 Testcontainers, 12 frontend \u043d\u0430 Vitest, E2E \u043d\u0430 Playwright<\/p>\n<\/li>\n<li>\n<p>GitHub Actions CI<\/p>\n<\/li>\n<\/ul>\n<p>\u0427\u0442\u043e \u0435\u0449\u0451 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043d\u043e:<\/p>\n<ul>\n<li>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 Double Ratchet \u0441 DH ratchet step \u0438 break-in recovery<\/p>\n<\/li>\n<li>\n<p>\u0420\u043e\u0442\u0430\u0446\u0438\u044f signed prekey \u0438 \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u043e\u0435 \u043f\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 one-time prekeys<\/p>\n<\/li>\n<li>\n<p>\u0411\u043e\u043b\u0435\u0435 \u0441\u0442\u0440\u043e\u0433\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0445 \u043a\u043b\u044e\u0447\u0435\u0439 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435: non-extractable CryptoKey + IndexedDB<\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u043f\u043e\u0434\u043c\u0435\u043d\u044b frontend-\u043a\u043e\u0434\u0430: \u043f\u043e\u0434\u043f\u0438\u0441\u044c \u0441\u0431\u043e\u0440\u043e\u043a, \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u0430\u044f \u0432\u0435\u0440\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430, reproducible builds<\/p>\n<\/li>\n<li>\n<p>Android-\u043a\u043b\u0438\u0435\u043d\u0442 \u0441 Android Keystore<\/p>\n<\/li>\n<li>\n<p>\u0420\u0435\u0430\u043b\u044c\u043d\u044b\u0439 SMS-\u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440 \u0432\u043c\u0435\u0441\u0442\u043e \u043a\u043e\u0434\u0430 \u0432 backend-\u043b\u043e\u0433\u0430\u0445<\/p>\n<\/li>\n<li>\n<p>Push-\u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0431\u0435\u0437 \u0443\u0442\u0435\u0447\u043a\u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439<\/p>\n<\/li>\n<li>\n<p>\u0411\u043e\u043b\u0435\u0435 \u0441\u0442\u0440\u043e\u0433\u0430\u044f metadata-\u043c\u043e\u0434\u0435\u043b\u044c \u0434\u043b\u044f \u0433\u0440\u0443\u043f\u043f\u043e\u0432\u044b\u0445 \u0447\u0430\u0442\u043e\u0432<\/p>\n<\/li>\n<\/ul>\n<hr\/>\n<h3>\u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u0438\u043d\u0441\u0430\u0439\u0442<\/h3>\n<p><strong>E2EE \u2014 \u044d\u0442\u043e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u0430 \u043d\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430.<\/strong><\/p>\n<p>\u041d\u0435\u043b\u044c\u0437\u044f \u0432\u0437\u044f\u0442\u044c \u043e\u0431\u044b\u0447\u043d\u044b\u0439 Spring Boot \u0447\u0430\u0442 \u0438 \u043f\u0440\u043e\u0441\u0442\u043e \u201c\u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u201d. \u041d\u0443\u0436\u043d\u043e \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b backend \u043d\u0435 \u0431\u044b\u043b \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u043c \u0434\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u0439 \u0437\u043e\u043d\u044b: \u043e\u043d \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c plaintext, \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0438\u043c\u0435\u0442\u044c \u043a\u043b\u044e\u0447\u0438 \u0438 \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0443\u043c\u0435\u0442\u044c \u043f\u0435\u0440\u0435\u0441\u043e\u0431\u0438\u0440\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438\u0437 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0431\u0430\u0437\u0435.<\/p>\n<p>\u042d\u0442\u043e \u043c\u0435\u043d\u044f\u0435\u0442 \u043f\u043e\u0447\u0442\u0438 \u0432\u0441\u0451:<\/p>\n<p>\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0411\u0414 \u2014 \u0432\u043c\u0435\u0441\u0442\u043e \u0442\u0435\u043a\u0441\u0442\u0430 \u043f\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f encrypted envelopes;<\/p>\n<p>API \u2014 \u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0442\u0434\u0430\u0451\u0442 <code>[encrypted]<\/code>, \u0430 \u043d\u0435 preview \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f;<\/p>\n<p>WebSocket \u2014 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u0438\u0434\u0451\u0442 \u043d\u0435 \u043f\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e, \u0430 \u043f\u043e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443;<\/p>\n<p>\u043c\u0443\u043b\u044c\u0442\u0438\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u2014 \u043e\u0434\u043d\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e ciphertext-\u043a\u043e\u043d\u0432\u0435\u0440\u0442\u043e\u0432;<\/p>\n<p>frontend \u2014 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0439 \u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0447\u0430\u0441\u0442\u044c\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u044b, \u0430 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e UI.<\/p>\n<p>\u0412\u0442\u043e\u0440\u043e\u0439 \u0438\u043d\u0441\u0430\u0439\u0442: \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440 \u2014 \u044d\u0442\u043e \u043d\u0435 \u201c\u0447\u0430\u0442 \u0441 WebSocket\u201d. \u0412 E2EE-\u043c\u043e\u0434\u0435\u043b\u0438 \u044d\u0442\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u043e\u0432 \u0441 \u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u0435\u0439 \u043f\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c. \u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u044d\u0442\u043e \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0448\u044c, \u043c\u043d\u043e\u0433\u0438\u0435 \u0441\u0442\u0440\u0430\u043d\u043d\u044b\u0435 \u043d\u0430 \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0437\u0433\u043b\u044f\u0434 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0442\u0441\u044f \u043b\u043e\u0433\u0438\u0447\u043d\u044b\u043c\u0438.<\/p>\n<hr\/>\n<h3>\u0420\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439<\/h3>\n<p>\u041a\u043e\u0434 \u043e\u0442\u043a\u0440\u044b\u0442:<a href=\"https:\/\/github.com\/vaazhen\/chaos-messenger\" rel=\"noopener noreferrer nofollow\"> github.com\/vaazhen\/chaos-messenger<\/a><\/p>\n<p>\u0412 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u0435\u0441\u0442\u044c README \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c \u0438 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u043c, \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u044b, \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442\u044b, security audit, Docker Compose \u0438 \u0437\u0430\u043f\u0443\u0441\u043a \u043e\u0434\u043d\u043e\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439.<\/p>\n<p>\u041f\u0440\u043e\u0435\u043a\u0442 \u043d\u0435 \u043f\u0440\u0435\u0442\u0435\u043d\u0434\u0443\u0435\u0442 \u043d\u0430 \u0443\u0440\u043e\u0432\u0435\u043d\u044c production-\u043a\u0440\u0438\u043f\u0442\u043e\u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430 \u0432\u0440\u043e\u0434\u0435 Signal. \u042d\u0442\u043e \u0443\u0447\u0435\u0431\u043d\u044b\u0439 \u0438 \u0438\u043d\u0436\u0435\u043d\u0435\u0440\u043d\u044b\u0439 open-source \u043f\u0440\u043e\u0442\u043e\u0442\u0438\u043f, \u0446\u0435\u043b\u044c \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u2014 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a E2EE \u043c\u0435\u043d\u044f\u0435\u0442 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443 backend, frontend \u0438 realtime-\u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0434\u0435\u043b\u0430\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0445\u043e\u0436\u0435\u0435 \u2014 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u0441\u0440\u0430\u0432\u043d\u0438\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434\u044b \u043a \u0440\u043e\u0442\u0430\u0446\u0438\u0438 prekey-\u043e\u0432, \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044e non-extractable \u043a\u043b\u044e\u0447\u0435\u0439 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 DH ratchet step. \u0412\u043e\u043f\u0440\u043e\u0441\u044b \u0438 \u043a\u0440\u0438\u0442\u0438\u043a\u0430 \u043f\u0440\u0438\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442\u0441\u044f.<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1030854\/\">https:\/\/habr.com\/ru\/articles\/1030854\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440.\u042f Java-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0438 \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u044e \u0441 backend: Spring Boot, \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445, \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f, WebSocket \u2014 \u0432\u0441\u0451 \u0442\u043e, \u0447\u0442\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0437\u0430 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u043c.\u0412 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043c\u043e\u043c\u0435\u043d\u0442 \u044f \u043f\u043e\u0439\u043c\u0430\u043b \u0441\u0435\u0431\u044f \u043d\u0430 \u043c\u044b\u0441\u043b\u0438: \u044f \u043a\u0430\u0436\u0434\u044b\u0439 \u0434\u0435\u043d\u044c \u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0441\u044c \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430\u043c\u0438, \u043d\u043e \u043f\u043b\u043e\u0445\u043e \u043f\u043e\u043d\u0438\u043c\u0430\u044e, \u043a\u0430\u043a \u043e\u043d\u0438 \u0443\u0441\u0442\u0440\u043e\u0435\u043d\u044b \u0432\u043d\u0443\u0442\u0440\u0438. \u041e\u043a\u0435\u0439, JWT, WebSocket, PostgreSQL, Redis \u2014 \u044d\u0442\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u043e. \u041d\u043e \u0447\u0442\u043e \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442 \u0444\u0440\u0430\u0437\u0430 \u201cend-to-end encryption\u201d? \u041a\u0430\u043a \u0441\u0435\u0440\u0432\u0435\u0440 \u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u0435\u0441\u043b\u0438 \u043e\u043d \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0438\u0445 \u0447\u0438\u0442\u0430\u0442\u044c? \u0413\u0434\u0435 \u0436\u0438\u0432\u0443\u0442 \u043a\u043b\u044e\u0447\u0438? \u0427\u0442\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432 \u0431\u0430\u0437\u0435? \u0427\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442, \u0435\u0441\u043b\u0438 \u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0434\u0432\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430?\u0420\u0435\u0448\u0438\u043b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0443. \u041d\u0430\u043f\u0438\u0441\u0430\u043b \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440 \u0441 \u043d\u0443\u043b\u044f. \u041d\u0430\u0437\u0432\u0430\u043b Chaos Messenger.\u0421\u0440\u0430\u0437\u0443 \u0447\u0435\u0441\u0442\u043d\u043e: \u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u044f \u0438\u0437\u0443\u0447\u0430\u043b \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 Claude \u0438 ChatGPT \u2014 \u0447\u0438\u0442\u0430\u043b \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 X3DH \u0438 Double Ratchet, \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b \u043f\u0440\u0438\u043c\u0435\u0440\u044b, \u0437\u0430\u0434\u0430\u0432\u0430\u043b \u0432\u043e\u043f\u0440\u043e\u0441\u044b, \u043f\u043e\u043a\u0430 \u043d\u0435 \u0441\u043b\u043e\u0436\u0438\u043b\u0430\u0441\u044c \u0446\u0435\u043b\u044c\u043d\u0430\u044f \u043a\u0430\u0440\u0442\u0438\u043d\u0430. Frontend \u0442\u043e\u0436\u0435 \u0434\u0435\u043b\u0430\u043b\u0441\u044f \u0441 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u043e\u043c\u043e\u0449\u044c\u044e ChatGPT: \u044f backend-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, React \u0434\u043b\u044f \u043c\u0435\u043d\u044f \u043d\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0441\u0440\u0435\u0434\u0430. \u041d\u043e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430, backend, \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f WebCrypto, \u043c\u043e\u0434\u0435\u043b\u044c \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u043e\u0432, \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u2014 \u043c\u043e\u0438.\u0414\u043b\u044f \u043c\u0435\u043d\u044f AI \u0437\u0434\u0435\u0441\u044c \u0431\u044b\u043b \u043d\u0435 \u0437\u0430\u043c\u0435\u043d\u043e\u0439 \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f, \u0430 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u043c \u2014 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043a\u0430\u043a \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f, Stack Overflow \u0438 \u0440\u0435\u0432\u044c\u044e \u043a\u043e\u043b\u043b\u0435\u0433. \u0411\u0435\u0437 \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f threat model \u0438 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b \u0442\u0430\u043a\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043d\u0435 \u0441\u043e\u0431\u0440\u0430\u0442\u044c.\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 E2EE \u0438\u0437\u043d\u0443\u0442\u0440\u0438: \u043a\u0430\u043a \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0441\u0435\u0441\u0441\u0438\u044f \u0447\u0435\u0440\u0435\u0437 X3DH, \u043a\u0430\u043a \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u0447\u0435\u0440\u0435\u0437 Symmetric Ratchet, \u043f\u043e\u0447\u0435\u043c\u0443 \u0441\u0435\u0440\u0432\u0435\u0440 \u0445\u0440\u0430\u043d\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u044b, \u0438 \u043a\u0430\u043a\u0438\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u044f \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043b \u043f\u043e \u0434\u043e\u0440\u043e\u0433\u0435.\u0421\u0442\u0435\u043a: Spring Boot 3, React 18, WebCrypto API, PostgreSQL, Redis, WebSocket\/STOMP, Prometheus, Grafana.\u0412\u0430\u0436\u043d\u0430\u044f \u043e\u0433\u043e\u0432\u043e\u0440\u043a\u0430 \u043f\u0440\u043e web-E2EE\u041a\u043e\u0433\u0434\u0430 \u044f \u0433\u043e\u0432\u043e\u0440\u044e, \u0447\u0442\u043e \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u044f \u0438\u043c\u0435\u044e \u0432 \u0432\u0438\u0434\u0443 backend, \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445, WebSocket-\u0441\u043b\u043e\u0439 \u0438 \u0443\u0436\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0435 ciphertext-\u043a\u043e\u043d\u0432\u0435\u0440\u0442\u044b. \u0423 \u043d\u0438\u0445 \u043d\u0435\u0442 \u043a\u043b\u044e\u0447\u0435\u0439 \u0438 plaintext.\u041d\u043e \u0443 web-E2EE \u0435\u0441\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430: frontend-\u043a\u043e\u0434 \u0442\u043e\u0436\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u0422\u0435\u043e\u0440\u0435\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0435\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0434\u0430\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0451\u043d\u043d\u044b\u0439 JavaScript, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u043a\u0440\u0430\u0434\u0451\u0442 \u043a\u043b\u044e\u0447\u0438 \u0438\u043b\u0438 plaintext \u0434\u043e \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f. \u042d\u0442\u043e \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u043d\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u043c\u043e\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0430 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438 \u0432 \u0446\u0435\u043b\u043e\u043c.\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u0430\u044f \u0444\u043e\u0440\u043c\u0443\u043b\u0438\u0440\u043e\u0432\u043a\u0430 \u0442\u0430\u043a\u0430\u044f: backend \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043a\u043b\u044e\u0447\u0438 \u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u0436\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u043b\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. \u0417\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u043f\u043e\u0434\u043c\u0435\u043d\u044b \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438: \u043f\u043e\u0434\u043f\u0438\u0441\u044c \u0441\u0431\u043e\u0440\u043e\u043a, \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u0430\u044f \u0432\u0435\u0440\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043a\u043b\u0438\u0435\u043d\u0442\u0430, desktop\/mobile-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, reproducible builds.\u041f\u043e\u0447\u0435\u043c\u0443 \u043e\u0431\u044b\u0447\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442\u0411\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e &#171;\u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u043e\u0432&#187; \u043d\u0430 GitHub \u0432\u044b\u0433\u043b\u044f\u0434\u044f\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a:message.setContent(request.getText());messageRepository.save(message);\u0421\u0435\u0440\u0432\u0435\u0440 \u0437\u043d\u0430\u0435\u0442 \u0432\u0441\u0451. \u0412\u0438\u0434\u0438\u0442 \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435. \u0415\u0441\u043b\u0438 \u0411\u0414 \u0443\u0442\u0435\u043a\u043b\u0430 \u2014 \u0443\u0442\u0435\u043a\u043b\u0430 \u0432\u0441\u044f \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u043a\u0430. \u0415\u0441\u043b\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u0437\u043b\u043e\u043c\u0430\u043b\u0438 \u2014 \u0447\u0438\u0442\u0430\u0439 \u0447\u0442\u043e \u0445\u043e\u0447\u0435\u0448\u044c. \u0415\u0441\u043b\u0438 \u0437\u0430\u0432\u0442\u0440\u0430 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f \u0440\u0435\u0448\u0438\u0442 \u043f\u0440\u043e\u0434\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u2014 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043c\u0435\u0448\u0430\u0435\u0442.E2EE \u0440\u0435\u0448\u0430\u0435\u0442 \u044d\u0442\u043e \u0440\u0430\u0434\u0438\u043a\u0430\u043b\u044c\u043d\u043e: backend \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043a\u043b\u044e\u0447\u0438 \u0438 \u043d\u0435 \u0445\u0440\u0430\u043d\u0438\u0442 plaintext. \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0448\u0438\u0444\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u0435\u043b\u044f \u0434\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0432 \u0441\u0435\u0442\u044c, \u0430 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f.\u042d\u0442\u043e \u0443\u0436\u0435 \u043d\u0435 \u0432\u043e\u043f\u0440\u043e\u0441 \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u0438 \u043a\u043e\u043d\u0444\u0438\u0434\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0432 \u0441\u0442\u0438\u043b\u0435 \u201c\u043c\u044b \u043e\u0431\u0435\u0449\u0430\u0435\u043c \u043d\u0435 \u0447\u0438\u0442\u0430\u0442\u044c\u201d. \u042d\u0442\u043e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043d\u043e\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435: \u0435\u0441\u043b\u0438 \u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0435\u0442 \u043a\u043b\u044e\u0447\u0430, \u043e\u043d \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044c ciphertext \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0432 \u0442\u0435\u043a\u0441\u0442.\u0417\u0432\u0443\u0447\u0438\u0442 \u043a\u0430\u043a \u043c\u0430\u0433\u0438\u044f. \u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u2014 \u0434\u0432\u0430 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0430 \u0438 \u043d\u0435\u043c\u043d\u043e\u0433\u043e WebCrypto.\u0413\u043b\u0430\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f: \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u044b\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c \u0447\u0442\u043e \u0410\u043b\u0438\u0441\u0430 \u0445\u043e\u0447\u0435\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0411\u043e\u0431\u0443. \u0412\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u043f\u0438\u0441\u044c\u043c\u043e \u043d\u0430 \u0441\u0442\u043e\u043b \u0438 \u043d\u0430\u0434\u0435\u044f\u0442\u044c\u0441\u044f \u0447\u0442\u043e \u043d\u0438\u043a\u0442\u043e \u043d\u0435 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0435\u0442 \u2014 \u043e\u043d\u0430 \u043a\u043b\u0430\u0434\u0451\u0442 \u0435\u0433\u043e \u0432 \u0437\u0430\u043f\u0435\u0447\u0430\u0442\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043d\u0432\u0435\u0440\u0442. \u041a\u043e\u043d\u0432\u0435\u0440\u0442 \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u0411\u043e\u0431 \u0441\u0432\u043e\u0438\u043c \u043a\u043b\u044e\u0447\u043e\u043c. \u0421\u0435\u0440\u0432\u0435\u0440 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442 \u043a\u043e\u043d\u0432\u0435\u0440\u0442 \u043d\u0435 \u0437\u0430\u0433\u043b\u044f\u0434\u044b\u0432\u0430\u044f \u0432\u043d\u0443\u0442\u0440\u044c.\u0418\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u043a\u043e\u0434\u0435. \u0412 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0443 \u043c\u0435\u043d\u044f \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:messages.content = &#8216;[encrypted]&#8217;  &#8212; \u0441\u0435\u0440\u0432\u0435\u0440 \u043d\u0435 \u0437\u043d\u0430\u0435\u0442 \u0447\u0442\u043e \u0432\u043d\u0443\u0442\u0440\u0438message_envelopes.ciphertext = &#8216;qzgHSg7zbwU6h8j8&#8230;&#8217;  &#8212; \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043e AES-GCM\u041a\u043e\u0433\u0434\u0430 \u044f \u0432\u043f\u0435\u0440\u0432\u044b\u0435 \u0443\u0432\u0438\u0434\u0435\u043b [encrypted] \u0432 \u0441\u0432\u043e\u0435\u0439 \u0411\u0414 \u0432\u043c\u0435\u0441\u0442\u043e \u0442\u0435\u043a\u0441\u0442\u0430 \u2014 \u0441\u0442\u0430\u043b\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0447\u0442\u043e \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0430\u043a\u043e\u043d\u0435\u0446 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e: \u0441\u0435\u0440\u0432\u0435\u0440 \u0441\u043e\u0437\u0434\u0430\u043b \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u0434\u043e\u0441\u0442\u0430\u0432\u0438\u043b \u0435\u0433\u043e, \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435, \u043d\u043e \u0442\u0430\u043a \u0438 \u043d\u0435 \u0443\u0437\u043d\u0430\u043b \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435.\u0410 \u0432\u043e\u0442 \u0447\u0442\u043e \u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043f\u0440\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0447\u0430\u0442\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 API:{  &#171;chatId&#187;: 32,  &#171;lastMessage&#187;: &#171;[encrypted]&#187;,  &#171;lastMessageAt&#187;: &#171;2026-04-28T22:27:35.537016&#187;}\u041d\u0435 ***. \u041d\u0435 [\u0441\u043a\u0440\u044b\u0442\u043e]. \u0411\u0443\u043a\u0432\u0430\u043b\u044c\u043d\u043e [encrypted] \u2014 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0435\u0442 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430. \u041f\u043e\u0437\u0436\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043a\u0430\u043a\u043e\u0439 \u0431\u0430\u0433 \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u0432\u044b\u0442\u0435\u043a.\u041e\u0442\u043a\u0443\u0434\u0430 \u0431\u0435\u0440\u0443\u0442\u0441\u044f \u043a\u043b\u044e\u0447\u0438: X3DH\u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u0432\u043e\u043f\u0440\u043e\u0441: \u043a\u0430\u043a \u0410\u043b\u0438\u0441\u0430 \u0438 \u0411\u043e\u0431 \u043f\u043e\u043b\u0443\u0447\u0430\u044e\u0442 \u043e\u0431\u0449\u0438\u0439 \u0441\u0435\u043a\u0440\u0435\u0442, \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u0440\u0430\u043d\u044c\u0448\u0435 \u043d\u0435 \u043e\u0431\u0449\u0430\u043b\u0438\u0441\u044c? \u0418 \u043a\u0430\u043a \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0441\u0435\u0440\u0432\u0435\u0440 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u043c\u043e\u0433 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u043d\u043e \u0441\u0430\u043c \u043d\u0435 \u0441\u043c\u043e\u0433 \u0432\u044b\u0447\u0438\u0441\u043b\u0438\u0442\u044c \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u043a\u043b\u044e\u0447?\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f X3DH \u2014 Extended Triple Diffie-Hellman, \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0438\u0437 \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u044b Signal. \u0415\u0433\u043e \u0437\u0430\u0434\u0430\u0447\u0430 \u2014 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043e\u0431\u0449\u0438\u0439 \u0441\u0435\u043a\u0440\u0435\u0442 \u043c\u0435\u0436\u0434\u0443 \u0434\u0432\u0443\u043c\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0434\u043e\u043b\u0433\u043e\u0441\u0440\u043e\u0447\u043d\u044b\u0435 \u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438.\u0427\u0442\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435\u041a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u043e\u043d \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u043f\u0430\u043a\u0435\u0442 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0445 \u043a\u043b\u044e\u0447\u0435\u0439:\/\/ crypto-engine.js \u2014 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043a\u043b\u044e\u0447\u0435\u0439 \u043f\u0440\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430async function buildNewDeviceBundle() {    const identity     = await generateX25519KeyPair(); \/\/ \u0434\u043e\u043b\u0433\u043e\u0441\u0440\u043e\u0447\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430    const signedPreKey = await generateX25519KeyPair(); \/\/ \u0434\u043e\u043b\u0436\u0435\u043d \u0440\u043e\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u0435\u0440\u0438\u043e\u0434\u0438\u0447\u0435\u0441\u043a\u0438    const oneTimePreKeys = [];    for (let i = 0; i &lt; 50; i++) {        const kp = await generateX25519KeyPair();        oneTimePreKeys.push({            preKeyId: 1000 + i,            publicKey: await exportRawPublicKey(kp.publicKey),            privateKeyPkcs8: await exportPkcs8PrivateKey(kp.privateKey)        });    }    \/\/ &#8230;}\u041d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0443\u0445\u043e\u0434\u044f\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u0447\u0430\u0441\u0442\u0438. \u041f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u044e\u0442\u0441\u044f \u0438 \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u2014 \u0438 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u043f\u043e\u043a\u0438\u0434\u0430\u044e\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0432 \u0441\u0435\u0442\u044c.\u0417\u0434\u0435\u0441\u044c \u0432\u0430\u0436\u043d\u043e \u0441\u043a\u0430\u0437\u0430\u0442\u044c \u0447\u0435\u0441\u0442\u043d\u043e: \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0445 \u043a\u043b\u044e\u0447\u0435\u0439 \u0432 localStorage \u2014 \u044d\u0442\u043e \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0438\u0441\u0441, \u0430 \u043d\u0435 \u0438\u0434\u0435\u0430\u043b\u044c\u043d\u0430\u044f \u043a\u0440\u0438\u043f\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043c\u043e\u0434\u0435\u043b\u044c.localStorage \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d JavaScript-\u043a\u043e\u0434\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u0415\u0441\u043b\u0438 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u043f\u043e\u044f\u0432\u0438\u0442\u0441\u044f XSS-\u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u044c \u0438\u043b\u0438 \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u043f\u043e\u0434\u043c\u0435\u043d\u0451\u043d\u043d\u044b\u0439 frontend-\u043a\u043e\u0434, \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0440\u0430\u0441\u0442\u044c. \u042d\u0442\u043e \u043d\u0435 \u043b\u043e\u043c\u0430\u0435\u0442 X3DH \u0438\u043b\u0438 AES-GCM, \u043d\u043e \u043b\u043e\u043c\u0430\u0435\u0442 \u043a\u043b\u0438\u0435\u043d\u0442\u0441\u043a\u0443\u044e \u0441\u0440\u0435\u0434\u0443, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u044d\u0442\u0438 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f.\u0411\u043e\u043b\u0435\u0435 \u0441\u0442\u0440\u043e\u0433\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u2014 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Web Crypto API \u0441 extractable: false, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 \u0436\u0438\u043b \u0432\u043d\u0443\u0442\u0440\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u043e\u0433\u043e crypto runtime \u0438 \u0435\u0433\u043e \u043d\u0435\u043b\u044c\u0437\u044f \u0431\u044b\u043b\u043e \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u0431\u0430\u0439\u0442\u044b. \u041d\u043e \u0443 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u0435\u0441\u0442\u044c \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c: \u043a\u043b\u044e\u0447\u0438 \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430\u043c\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441 IndexedDB, \u0430\u043a\u043a\u0443\u0440\u0430\u0442\u043d\u043e \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0438 \u043d\u0435 \u0441\u043b\u043e\u043c\u0430\u0442\u044c UX.\u0412 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043d\u044b\u0445 E2EE-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u044b\u0431\u0438\u0440\u0430\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430\u043c\u0438:\u0421\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u0432 localStorage \u0438\u043b\u0438 IndexedDB \u2014 \u043f\u0440\u043e\u0449\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c, \u043d\u043e \u043d\u0443\u0436\u043d\u043e \u043e\u0447\u0435\u043d\u044c \u0441\u0435\u0440\u044c\u0451\u0437\u043d\u043e \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u044c\u0441\u044f \u043a XSS \u0438 \u0446\u0435\u043b\u043e\u0441\u0442\u043d\u043e\u0441\u0442\u0438 frontend-\u043a\u043e\u0434\u0430.extractable: false + IndexedDB \u2014 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0435\u0435, \u043d\u043e \u0441\u043b\u043e\u0436\u043d\u0435\u0435 \u0432 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f.\u041d\u0430\u0442\u0438\u0432\u043d\u043e\u0435 secure storage \u0432\u0440\u043e\u0434\u0435 Android Keystore \u0438\u043b\u0438 iOS Secure Enclave \u2014 \u043b\u0443\u0447\u0448\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442 \u0434\u043b\u044f \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432, \u043d\u043e \u043e\u043d \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u043e\u0431\u044b\u0447\u043d\u043e\u043c\u0443 web-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e.\u0412 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 Chaos Messenger \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442. \u042d\u0442\u043e \u043e\u0441\u043e\u0437\u043d\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0438\u0441\u0441 \u0434\u043b\u044f pet\/open-source \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0443\u0434\u043e\u0431\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435. \u041f\u0435\u0440\u0435\u0445\u043e\u0434 \u043d\u0430 non-extractable \u043a\u043b\u044e\u0447\u0438 \u0438 \u0431\u043e\u043b\u0435\u0435 \u0441\u0442\u0440\u043e\u0433\u0443\u044e \u043c\u043e\u0434\u0435\u043b\u044c \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0442\u043e\u0438\u0442 \u0432 roadmap.\u041a\u043b\u044e\u0447\u0435\u0432\u043e\u0439 \u043c\u043e\u043c\u0435\u043d\u0442: backend \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u0438 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0435 ciphertext-\u043a\u043e\u043d\u0432\u0435\u0440\u0442\u044b. \u041d\u043e \u0437\u0430\u0449\u0438\u0442\u0430 \u043a\u043b\u044e\u0447\u0435\u0439 \u043d\u0430 \u043a\u043b\u0438\u0435\u043d\u0442\u0435 \u2014 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430, \u0438 \u0435\u0451 \u043d\u0435\u043b\u044c\u0437\u044f \u0447\u0435\u0441\u0442\u043d\u043e \u0437\u0430\u043c\u0430\u043b\u0447\u0438\u0432\u0430\u0442\u044c.\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0441\u0435\u0441\u0441\u0438\u0438\u041a\u043e\u0433\u0434\u0430 \u0410\u043b\u0438\u0441\u0430 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u043a\u0443 \u0441 \u0411\u043e\u0431\u043e\u043c \u0432\u043f\u0435\u0440\u0432\u044b\u0435, \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:\/\/ crypto-engine.js \u2014 X3DH \u0441\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u0438\u043d\u0438\u0446\u0438\u0430\u0442\u043e\u0440\u0430async function createInitiatorSessionWrapped(localBundle, targetDevice) {    const identityPrivate       = await importPkcs8PrivateKey(localBundle.identity.privateKeyPkcs8);    const ephemeral             = await generateX25519KeyPair(); \/\/ \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u043b\u044e\u0447 \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0441\u0435\u0441\u0441\u0438\u0438    const remoteIdentityPub     = await importRawPublicKey(targetDevice.identityPublicKey);    const remoteSignedPreKeyPub = await importRawPublicKey(targetDevice.signedPreKey.publicKey);    \/\/ X3DH \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e DH-\u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439.    \/\/ DH4 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0443 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f \u0435\u0441\u0442\u044c one-time prekey.    const dh1 = await derive32(identityPrivate,      remoteSignedPreKeyPub); \/\/ IK_alice \u00b7 SPK_bob    const dh2 = await derive32(ephemeral.privateKey, remoteIdentityPub);     \/\/ EK_alice \u00b7 IK_bob    const dh3 = await derive32(ephemeral.privateKey, remoteSignedPreKeyPub); \/\/ EK_alice \u00b7 SPK_bob    const parts = [dh1, dh2, dh3];    if (remoteOneTimePub) {        const dh4 = await derive32(ephemeral.privateKey, remoteOneTimePub);  \/\/ EK_alice \u00b7 OPK_bob        parts.push(dh4);    }    const combined = concat(&#8230;parts);    \/\/ \u0418\u0437 combined \u0447\u0435\u0440\u0435\u0437 HKDF \u0432\u044b\u0432\u043e\u0434\u0438\u043c rootKey \u0438 chainKey    const { rootKey, chainKey } = await deriveRootAndChainKey(combined);    \/\/ &#8230;}\u0412 \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u043e\u043c X3DH \u0447\u0435\u0442\u0432\u0451\u0440\u0442\u0430\u044f DH-\u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u0441 one-time prekey \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u0430: \u043e\u043d\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u044b\u0434\u0430\u043b \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0439 OPK \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u0435\u043b\u044f. \u0412 \u043c\u043e\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043f\u0443\u0431\u043b\u0438\u043a\u0443\u0435\u0442 \u043d\u0430\u0431\u043e\u0440 one-time prekeys \u043f\u0440\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0435\u0440\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431\u044b\u0447\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 DH4. \u0415\u0441\u043b\u0438 OPK \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u043b\u0438\u0441\u044c, \u0441\u0435\u0441\u0441\u0438\u044e \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043c\u043e\u0436\u043d\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 DH-\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b, \u043d\u043e \u044d\u0442\u043e \u0443\u0436\u0435 \u043c\u0435\u043d\u0435\u0435 \u0441\u0438\u043b\u044c\u043d\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442.\u0411\u043e\u0431, \u043f\u043e\u043b\u0443\u0447\u0438\u0432 \u043a\u043e\u043d\u0432\u0435\u0440\u0442 \u0441 \u044d\u0444\u0435\u043c\u0435\u0440\u043d\u044b\u043c \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c \u0410\u043b\u0438\u0441\u044b, \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u0442 \u0442\u0435 \u0436\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u043e \u0441\u0432\u043e\u0438\u043c\u0438 \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u043c\u0438 \u043a\u043b\u044e\u0447\u0430\u043c\u0438 \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0442\u043e\u0442 \u0436\u0435 \u0441\u0430\u043c\u044b\u0439 combined. \u041c\u0430\u0442\u0435\u043c\u0430\u0442\u0438\u043a\u0430 \u0441\u0438\u043c\u043c\u0435\u0442\u0440\u0438\u0447\u043d\u0430.\u0421\u0435\u0440\u0432\u0435\u0440 \u0432 \u044d\u0442\u043e\u0442 \u043c\u043e\u043c\u0435\u043d\u0442 \u0432\u0438\u0434\u0438\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0435 \u043a\u043b\u044e\u0447\u0438 \u0438 \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043a\u043e\u043d\u0432\u0435\u0440\u0442. \u041e\u043d \u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c \u043d\u0430\u0439\u0442\u0438 \u0434\u0440\u0443\u0433 \u0434\u0440\u0443\u0433\u0430, \u043d\u043e \u043d\u0435 \u0443\u0447\u0430\u0441\u0442\u0432\u0443\u0435\u0442 \u0432 \u0432\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0438 \u0441\u0435\u043a\u0440\u0435\u0442\u0430.\u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c combined \u0442\u043e\u043b\u044c\u043a\u043e \u0438\u0437 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0445 \u043a\u043b\u044e\u0447\u0435\u0439 \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e: \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c \u0437\u0434\u0435\u0441\u044c \u043e\u043f\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 Diffie-Hellman \u043d\u0430 Curve25519. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u0436\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0438 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c prekey bundle, \u043d\u043e \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u0442\u043e\u0442 \u0436\u0435 shared secret, \u0447\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.\u041a\u0430\u043a \u0448\u0438\u0444\u0440\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435: Symmetric RatchetX3DH \u0434\u0430\u0451\u0442 \u043d\u0430\u043c \u0441\u0442\u0430\u0440\u0442\u043e\u0432\u044b\u0439 chainKey. \u041d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043a\u043b\u044e\u0447 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u2014 \u043f\u043b\u043e\u0445\u0430\u044f \u0438\u0434\u0435\u044f. \u0415\u0441\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u0434\u0438\u043d \u043a\u043b\u044e\u0447 \u0434\u043b\u044f \u0432\u0441\u0435\u0439 \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u043a\u0438, \u043a\u043e\u043c\u043f\u0440\u043e\u043c\u0435\u0442\u0430\u0446\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u044e\u0447\u0430 \u0441\u0440\u0430\u0437\u0443 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0432\u0435\u0441\u044c \u043f\u043e\u0442\u043e\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439.\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u2014 \u0441\u0438\u043c\u043c\u0435\u0442\u0440\u0438\u0447\u043d\u044b\u0439 ratchet. \u041f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0446\u0435\u043f\u043e\u0447\u043a\u0430 \u043a\u043b\u044e\u0447\u0435\u0439 \u043f\u0440\u043e\u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044f \u0432\u043f\u0435\u0440\u0451\u0434:\/\/ crypto-engine.js \u2014 \u043e\u0434\u0438\u043d \u0448\u0430\u0433 \u0440atchetasync function ratchetStep(chainKeyBytes) {    const key = await crypto.subtle.importKey(        &#8216;raw&#8217;, chainKeyBytes,        { name: &#8216;HMAC&#8217;, &#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-478390","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/478390","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=478390"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/478390\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=478390"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=478390"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=478390"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}