miscomputation of ECP::ScalarMultiply() using 5.6.4-9
Affects | Status | Importance | Assigned to | Milestone | |
---|---|---|---|---|---|
libcrypto++ (Ubuntu) |
New
|
Undecided
|
Unassigned |
Bug Description
This issue was reported to the Security team over email and originally posted to https:/
> I typically never use Crypto++, but I had to yesterday, and I then experienced a strange behavior that I felt I had to somehow report. Having read your [security policy](https:/
>
> ### Background
>
> I used the default Crypto++ package provided by [Ubuntu 20.04.6 LTS (Focal Fossa)](https:/
>
> More specifically, Crypto++ was installed on the machine via `apt` as follows:
>
> ```
> $ sudo apt update && sudo apt upgrade
> (..)
> $ sudo apt install libcrypto++-dev
> (..)
> libcrypto++-dev is already the newest version (5.6.4-9build1).
> ```
>
> The package version 5.6.4 leads me to think that it installs the (old) v5.6.4 release of Crypto++ from [this GitHub repository](https:/
> ### The issue
>
> When using Crypto++ as provided by the above package, it seems `ECP::ScalarMul
>
> To exemplify, I obtain the below result:
>
> ```
> Q1.x = 333065903909305
> Q1.y = 516711634285624
>
> >> Q1 is *NOT* as expected.
> >> Q1 is *NOT* on E.
>
> Q2.x = 338987448638294
> Q2.y = 234836455830503
>
> >> Q2 is as expected.
> >> Q2 is on E.
>
> >> T1 is equal to T2 for d = 1.
> >> T1 is *NOT* equal to T2 for d = 2.
> >> T1 is *NOT* equal to T2 for d = 3.
> >> T1 is *NOT* equal to T2 for d = 4.
> >> T1 is *NOT* equal to T2 for d = 5.
> >> T1 is *NOT* equal to T2 for d = 6.
> >> T1 is *NOT* equal to T2 for d = 7.
> >> T1 is *NOT* equal to T2 for d = 8.
> >> T1 is *NOT* equal to T2 for d = 9.
> >> T1 is *NOT* equal to T2 for d = 10.
> >> T1 is *NOT* equal to T2 for d = 11.
> >> T1 is *NOT* equal to T2 for d = 12.
> >> T1 is *NOT* equal to T2 for d = 13.
> >> T1 is *NOT* equal to T2 for d = 14.
> >> T1 is *NOT* equal to T2 for d = 15.
> >> T1 is *NOT* equal to T2 for d = 16.
> >> T1 is *NOT* equal to T2 for d = 17.
> >> T1 is *NOT* equal to T2 for d = 18.
> >> T1 is *NOT* equal to T2 for d = 19.
> >> T1 is *NOT* equal to T2 for d = 20.
> >> T1 is *NOT* equal to T2 for d = 21.
> >> T1 is *NOT* equal to T2 for d = 22.
> >> T1 is *NOT* equal to T2 for d = 23.
> >> T1 is *NOT* equal to T2 for d = 24.
> >> T1 is *NOT* equal to T2 for d = 25.
> >> T1 is *NOT* equal to T2 for d = 26.
> >> T1 is *NOT* equal to T2 for d = 27.
> >> T1 is *NOT* equal to T2 for d = 28.
> >> T1 is *NOT* equal to T2 for d = 29.
> >> T1 is *NOT* equal to T2 for d = 30.
> >> T1 is *NOT* equal to T2 for d = 31.
> >> T1 is equal to T2 for d = 32.
> >> T1 is equal to T2 for d = 33.
> >> T1 is equal to T2 for d = 34.
>
> >> T1 is equal to T2 for d = 483838642090169
> ```
>
> The source code in `main.cpp` is as follows:
>
> ```c++
> #include <iostream>
>
> using std::cout;
> using std::endl;
>
> #include "cryptopp/ecp.h"
>
> using CryptoPP::Integer;
> using CryptoPP::ECPPoint;
> using CryptoPP::ECP;
>
> int main() {
> const Integer p("685636793819
>
> const Integer a("383404102904
> const Integer b("618624618293
>
> const ECP E = ECP(p, a, b);
>
> const Integer q("171409198454
>
> /* Note: The curve E has order r = 2^2 * q where q is prime. */
>
> const Integer x("497837296598
> const Integer y("189167452467
>
> const ECPPoint P = ECP::Point(x, y);
>
> /* Note: The point P is on E and of order r so it generates all of E. */
>
> /* Note: Let us now compute the point Q = [4] P of prime order q. */
>
> const Integer expected_
> const Integer expected_
>
> const Integer cofactor(4);
>
> const ECPPoint Q1 = E.ScalarMultiply(P, cofactor);
>
> std::cout << "Q1.x = " << Q1.x << std::endl;
> std::cout << "Q1.y = " << Q1.y << std::endl << std::endl;
>
> if ((expected_Qx == Q1.x) && (expected_Qy == Q1.y)) {
> std::cout << ">> Q1 is as expected." << std::endl;
> } else {
> std::cout << ">> Q1 is *NOT* as expected." << std::endl;
> }
>
> if (E.VerifyPoint(Q1)) {
> std::cout << ">> Q1 is on E." << std::endl;
> } else {
> std::cout << ">> Q1 is *NOT* on E." << std::endl;
> }
>
> std::cout << std::endl;
>
> /* Let us compute Q by a different function call and compare: */
>
> ECPPoint Q2;
> E.SimultaneousM
>
> std::cout << "Q2.x = " << Q2.x << std::endl;
> std::cout << "Q2.y = " << Q2.y << std::endl << std::endl;
>
> if ((expected_Qx == Q2.x) && (expected_Qy == Q2.y)) {
> std::cout << ">> Q2 is as expected." << std::endl;
> } else {
> std::cout << ">> Q2 is *NOT* as expected." << std::endl;
> }
>
> if (E.VerifyPoint(Q2)) {
> std::cout << ">> Q2 is on E." << std::endl;
> } else {
> std::cout << ">> Q2 is *NOT* on E." << std::endl;
> }
>
> std::cout << std::endl;
>
> /* It seems Crypto++ treats the case where the scalar is of bit length <= 5
> * differently from the case where it is of longer length, see:
> *
> * https:/
> *
> * Could this be the issue? Let us perform the below tests: */
>
> for (unsigned int i = 1; i <= 34; i++) {
> Integer d(i);
>
> /* Compute T = [d] P by two different function calls and compare: */
>
> const ECPPoint T1 = E.ScalarMultiply(P, d);
>
> ECPPoint T2;
> E.SimultaneousM
>
> if (T1 == T2) {
> std::cout << ">> T1 is equal to T2 for d = " << d << std::endl;
> } else {
> std::cout << ">> T1 is *NOT* equal to T2 for d = " << d << std::endl;
> }
> }
>
> std::cout << std::endl;
>
> /* Let us also try with a large scalar uniformly sampled from [0, r). */
>
> {
> Integer d("483838642090
>
> /* Compute T = [d] P by two different function calls and compare: */
>
> const ECPPoint T1 = E.ScalarMultiply(P, d);
>
> ECPPoint T2;
> E.SimultaneousM
>
> if (T1 == T2) {
> std::cout << ">> T1 is equal to T2 for d = " << d << std::endl;
> } else {
> std::cout << ">> T1 is *NOT* equal to T2 for d = " << d << std::endl;
> }
> }
> }
> ```
>
> To confirm that there was no issue with the specific Ubuntu installation, I setup a clean virtual Ubuntu 20.04.6 LTS machine and repeated the above steps. I was thus able to reproduce the erroneous behavior.
>
> To check whether this issue extends to other releases and architectures, I furthermore compiled `main.cpp` and ran the resulting executable under Ubuntu 22.04 LTS for ARM64 in a virtual machine. The correct expected output was then produced:
>
> ```
> $ g++ main.cpp -lcryptopp -o test
> $ ./test
> Q1.x = 338987448638294
> Q1.y = 234836455830503
>
> >> Q1 is as expected.
> >> Q1 is on E.
>
> Q2.x = 338987448638294
> Q2.y = 234836455830503
>
> >> Q2 is as expected.
> >> Q2 is on E.
>
> >> T1 is equal to T2 for d = 1.
> >> T1 is equal to T2 for d = 2.
> >> T1 is equal to T2 for d = 3.
> >> T1 is equal to T2 for d = 4.
> >> T1 is equal to T2 for d = 5.
> >> T1 is equal to T2 for d = 6.
> >> T1 is equal to T2 for d = 7.
> >> T1 is equal to T2 for d = 8.
> >> T1 is equal to T2 for d = 9.
> >> T1 is equal to T2 for d = 10.
> >> T1 is equal to T2 for d = 11.
> >> T1 is equal to T2 for d = 12.
> >> T1 is equal to T2 for d = 13.
> >> T1 is equal to T2 for d = 14.
> >> T1 is equal to T2 for d = 15.
> >> T1 is equal to T2 for d = 16.
> >> T1 is equal to T2 for d = 17.
> >> T1 is equal to T2 for d = 18.
> >> T1 is equal to T2 for d = 19.
> >> T1 is equal to T2 for d = 20.
> >> T1 is equal to T2 for d = 21.
> >> T1 is equal to T2 for d = 22.
> >> T1 is equal to T2 for d = 23.
> >> T1 is equal to T2 for d = 24.
> >> T1 is equal to T2 for d = 25.
> >> T1 is equal to T2 for d = 26.
> >> T1 is equal to T2 for d = 27.
> >> T1 is equal to T2 for d = 28.
> >> T1 is equal to T2 for d = 29.
> >> T1 is equal to T2 for d = 30.
> >> T1 is equal to T2 for d = 31.
> >> T1 is equal to T2 for d = 32.
> >> T1 is equal to T2 for d = 33.
> >> T1 is equal to T2 for d = 34.
>
> >> T1 is equal to T2 for d = 483838642090169
> ```
>
> To better try to understand what is going on, I [downloaded Crypto++ releases](https:/
>
> I did expect to be able to reproduce the error at least for v5.6.4, if this is the version from which the Ubuntu package was built. When I could not, I computed a diff between the header files installed by the Ubuntu package and the header files for the v5.6.4 release from this GitHub repository, and there appears to be some differences.
>
> So in conclusion, I am not entirely sure which version of Crypto++ is in the Ubuntu repository, and how it was compiled, tested, etc. But it seems that it is not working properly? I find this all a bit surprising. Could someone else please confirm this? Or let me know if I am making some mistake in my code, given that I usually never use Crypto++. Assuming I did not make a mistake and that this really is an issue, further actions may then of course be necessary.
>
> (On a side note, I would expect there to be unit tests that cover the small scalar case, since the code branches depending on the bit length of the scalar. Assuming that this is the case, and that these tests were run, it seems strange to me that this issue would not have been detected when the Ubuntu package was built.)
With fresh amd64 VMs using the latest Ubuntu point releases, I was able to reproduce your report on Ubuntu Focal 20.04.06 (`libcrypto++` version 5.6.4-9build1). Both Bionic 18.04.06 (`libcrypto++` version 5.6.4-8) and Jammy 22.04.04 (`libcrypto++` version 8.6.0-2ubuntu1) had the expected result.
Also on Ubuntu Focal 20.04.04, I installed [Debian's `libcrypto++` version 5.6.4-9](https:/ /snapshot. debian. org/package/ libcrypto+ +/5.6.4- 9/) directly. This version also has the error. Debian's `libcrypto++` version immediately prior [5.6.4-8](https:/ /snapshot. debian. org/package/ libcrypto+ +/5.6.4- 8/) is not affected. The Debian version afterwards, [5.6.4-10](https:/ /snapshot. debian. org/package/ libcrypto+ +/5.6.4- 10/), is affected, but [6.1.0-1](https:/ /snapshot. debian. org/package/ libcrypto+ +/6.1.0- 1/) is not.
So, the issue is only known to affect packages based on Debian `libcrypto++` 5.6.4-9 and 5.6.4-10.