diff --git a/2021/12/24/Endianness/index.html b/2021/12/24/Endianness/index.html index 363d7b8..2360db8 100644 --- a/2021/12/24/Endianness/index.html +++ b/2021/12/24/Endianness/index.html @@ -456,14 +456,14 @@

Network Order

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2021/12/26/IPv4-IPv6-checksum/index.html b/2021/12/26/IPv4-IPv6-checksum/index.html index 3cac8af..90b14a3 100644 --- a/2021/12/26/IPv4-IPv6-checksum/index.html +++ b/2021/12/26/IPv4-IPv6-checksum/index.html @@ -493,14 +493,14 @@

UDP-Lite Application

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2021/12/29/RPi-NAS-Plex/index.html b/2021/12/29/RPi-NAS-Plex/index.html index fe21c01..438573b 100644 --- a/2021/12/29/RPi-NAS-Plex/index.html +++ b/2021/12/29/RPi-NAS-Plex/index.html @@ -787,14 +787,14 @@

Project Summary

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2022/01/22/Python-Textbook-RSA/index.html b/2022/01/22/Python-Textbook-RSA/index.html index 0fb650b..05230d1 100644 --- a/2022/01/22/Python-Textbook-RSA/index.html +++ b/2022/01/22/Python-Textbook-RSA/index.html @@ -545,14 +545,14 @@

Performance Tests

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2022/03/13/IPv6-Addressing/index.html b/2022/03/13/IPv6-Addressing/index.html index 806588d..a525408 100644 --- a/2022/03/13/IPv6-Addressing/index.html +++ b/2022/03/13/IPv6-Addressing/index.html @@ -835,14 +835,14 @@

IPv6 Core Protocol RFC List

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2022/04/22/ASAN-intro/index.html b/2022/04/22/ASAN-intro/index.html index dcedb55..72a4bf6 100644 --- a/2022/04/22/ASAN-intro/index.html +++ b/2022/04/22/ASAN-intro/index.html @@ -611,14 +611,14 @@

UAR Test

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2022/08/20/picoCTF-Sum-O-Primes/index.html b/2022/08/20/picoCTF-Sum-O-Primes/index.html index c48bb90..b0ba681 100644 --- a/2022/08/20/picoCTF-Sum-O-Primes/index.html +++ b/2022/08/20/picoCTF-Sum-O-Primes/index.html @@ -499,14 +499,14 @@

Quick Solution

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2022/11/10/Stop-TLS1-0-TLS1-1/index.html b/2022/11/10/Stop-TLS1-0-TLS1-1/index.html index eeb75ba..f011c9a 100644 --- a/2022/11/10/Stop-TLS1-0-TLS1-1/index.html +++ b/2022/11/10/Stop-TLS1-0-TLS1-1/index.html @@ -635,14 +635,14 @@

References

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2022/11/21/DH-and-RSA/index.html b/2022/11/21/DH-and-RSA/index.html index 665e856..fb0454d 100644 --- a/2022/11/21/DH-and-RSA/index.html +++ b/2022/11/21/DH-and-RSA/index.html @@ -758,14 +758,14 @@

DHE-RSA Cipher Suite

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2023/03/10/uClibc-tips/index.html b/2023/03/10/uClibc-tips/index.html index ade9475..e81efd8 100644 --- a/2023/03/10/uClibc-tips/index.html +++ b/2023/03/10/uClibc-tips/index.html @@ -41,8 +41,8 @@ - + @@ -412,8 +412,8 @@

DNS Security Patch

- +
@@ -482,14 +482,14 @@

DNS Security Patch

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2023/03/16/RSA-attack-defense/index.html b/2023/03/16/RSA-attack-defense/index.html index 35621e4..e18d96b 100644 --- a/2023/03/16/RSA-attack-defense/index.html +++ b/2023/03/16/RSA-attack-defense/index.html @@ -550,14 +550,14 @@

Broadcast Attack

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2023/08/21/TLS1-3-intro/index.html b/2023/08/21/TLS1-3-intro/index.html index bb68996..f5cdda1 100644 --- a/2023/08/21/TLS1-3-intro/index.html +++ b/2023/08/21/TLS1-3-intro/index.html @@ -619,14 +619,14 @@

Lighttpd Web Server

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2023/11/14/Fermats-Little-Theorem/index.html b/2023/11/14/Fermats-Little-Theorem/index.html index e02a6a3..868e2ac 100644 --- a/2023/11/14/Fermats-Little-Theorem/index.html +++ b/2023/11/14/Fermats-Little-Theorem/index.html @@ -508,14 +508,14 @@

Optimized RSA Decryption

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2023/11/17/RSA-attack-defense-2/index.html b/2023/11/17/RSA-attack-defense-2/index.html index e42f61b..ec1cf79 100644 --- a/2023/11/17/RSA-attack-defense-2/index.html +++ b/2023/11/17/RSA-attack-defense-2/index.html @@ -526,14 +526,14 @@

Attack Workflow

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/01/19/Purdue-MA265-2022-Fall-Midterm1/index.html b/2024/01/19/Purdue-MA265-2022-Fall-Midterm1/index.html index 5bb15ec..f460644 100644 --- a/2024/01/19/Purdue-MA265-2022-Fall-Midterm1/index.html +++ b/2024/01/19/Purdue-MA265-2022-Fall-Midterm1/index.html @@ -719,14 +719,14 @@

Summary

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/01/23/Purdue-MA265-2023-Spring-Midterm1/index.html b/2024/01/23/Purdue-MA265-2023-Spring-Midterm1/index.html index 56b2fa2..8738601 100644 --- a/2024/01/23/Purdue-MA265-2023-Spring-Midterm1/index.html +++ b/2024/01/23/Purdue-MA265-2023-Spring-Midterm1/index.html @@ -705,14 +705,14 @@

Summary

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/01/28/Purdue-MA265-2023-Fall-Midterm1/index.html b/2024/01/28/Purdue-MA265-2023-Fall-Midterm1/index.html index 3627ce2..75566fa 100644 --- a/2024/01/28/Purdue-MA265-2023-Fall-Midterm1/index.html +++ b/2024/01/28/Purdue-MA265-2023-Fall-Midterm1/index.html @@ -696,14 +696,14 @@

Properties of Determinants

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/02/10/Purdue-MA265-2022-Fall-Midterm2/index.html b/2024/02/10/Purdue-MA265-2022-Fall-Midterm2/index.html index 4ad05ca..c903493 100644 --- a/2024/02/10/Purdue-MA265-2022-Fall-Midterm2/index.html +++ b/2024/02/10/Purdue-MA265-2022-Fall-Midterm2/index.html @@ -831,14 +831,14 @@

Summary

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/02/24/Purdue-CS240-2018-Fall-Midterm1/index.html b/2024/02/24/Purdue-CS240-2018-Fall-Midterm1/index.html index f7ef6f3..9736933 100644 --- a/2024/02/24/Purdue-CS240-2018-Fall-Midterm1/index.html +++ b/2024/02/24/Purdue-CS240-2018-Fall-Midterm1/index.html @@ -491,14 +491,14 @@

Problem 4 (20 pts)

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/02/25/Purdue-CS240-2022-2023-Summer-Midterm/index.html b/2024/02/25/Purdue-CS240-2022-2023-Summer-Midterm/index.html index 6920d52..8b76268 100644 --- a/2024/02/25/Purdue-CS240-2022-2023-Summer-Midterm/index.html +++ b/2024/02/25/Purdue-CS240-2022-2023-Summer-Midterm/index.html @@ -588,14 +588,14 @@

Bonus Problem (10 pts)

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/02/28/C-Prog-Exam-Review-Practices-1/index.html b/2024/02/28/C-Prog-Exam-Review-Practices-1/index.html index e1caaf3..38a3e77 100644 --- a/2024/02/28/C-Prog-Exam-Review-Practices-1/index.html +++ b/2024/02/28/C-Prog-Exam-Review-Practices-1/index.html @@ -637,14 +637,14 @@

Basic Pointer Operations

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/02/29/Purdue-MA265-2022-Spring-Midterm2/index.html b/2024/02/29/Purdue-MA265-2022-Spring-Midterm2/index.html index c953dbf..58f88a7 100644 --- a/2024/02/29/Purdue-MA265-2022-Spring-Midterm2/index.html +++ b/2024/02/29/Purdue-MA265-2022-Spring-Midterm2/index.html @@ -639,14 +639,14 @@

Problem 10 (10 points)

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/02/29/Purdue-MA265-2023-Spring-Midterm2/index.html b/2024/02/29/Purdue-MA265-2023-Spring-Midterm2/index.html index ee9fbf3..d26e9b0 100644 --- a/2024/02/29/Purdue-MA265-2023-Spring-Midterm2/index.html +++ b/2024/02/29/Purdue-MA265-2023-Spring-Midterm2/index.html @@ -644,14 +644,14 @@

Summary

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/03/24/Purdue-CS240-2022-Summer-Final/index.html b/2024/03/24/Purdue-CS240-2022-Summer-Final/index.html index a00609e..caabd53 100644 --- a/2024/03/24/Purdue-CS240-2022-Summer-Final/index.html +++ b/2024/03/24/Purdue-CS240-2022-Summer-Final/index.html @@ -496,14 +496,14 @@

Bonus Problem (10 pts)

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/03/26/C-Prog-Exam-Review-Practices-2/index.html b/2024/03/26/C-Prog-Exam-Review-Practices-2/index.html index 2fd4791..a7b098c 100644 --- a/2024/03/26/C-Prog-Exam-Review-Practices-2/index.html +++ b/2024/03/26/C-Prog-Exam-Review-Practices-2/index.html @@ -586,14 +586,14 @@

C Preprocessor and Libraries

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/03/27/Purdue-CS240-2018-Fall-Midterm2/index.html b/2024/03/27/Purdue-CS240-2018-Fall-Midterm2/index.html index 808d63b..2188f88 100644 --- a/2024/03/27/Purdue-CS240-2018-Fall-Midterm2/index.html +++ b/2024/03/27/Purdue-CS240-2018-Fall-Midterm2/index.html @@ -473,14 +473,14 @@

Problem 3 (30 pts)

Symbols count total: - 436k + 441k Reading time total ≈ - 6:36 + 6:41
diff --git a/2024/04/18/Purdue-MA265-2022-Spring-Final/index.html b/2024/04/18/Purdue-MA265-2022-Spring-Final/index.html index ef0a602..7ce46d3 100644 --- a/2024/04/18/Purdue-MA265-2022-Spring-Final/index.html +++ b/2024/04/18/Purdue-MA265-2022-Spring-Final/index.html @@ -59,7 +59,7 @@ - + @@ -301,7 +301,7 @@

- +

@@ -367,7 +367,7 @@

Problem 1

Problem 1 Solution

-

Start with the augmented matrix of the system, do row reduction like below

+

Start with the augmented matrix of the system, and do row reduction like the below

\[ \left[\begin{array}{ccc|c}1&2&3&16\\2&0&-2&14\\3&2&1&3a\end{array}\right]\sim \left[\begin{array}{ccc|c}1&2&3&16\\0&-4&-8&-18\\0&-4&-8&3a-48\end{array}\right]\sim @@ -380,13 +380,11 @@

Problem 2

Problem 2 Solution

-

According to the properties of determinants:

-
-

Let A be a square matrix.
-a. If a multiple of one row of \(A\) is added to another row to produce a matrix \(B\),then \(\det B =\det A\).
+

First review the properties of determinants:
+>Let A be a square matrix.
+> a. If a multiple of one row of \(A\) is added to another row to produce a matrix \(B\), then \(\det B =\det A\).
b. If two rows of \(A\) are interchanged to produce \(B\), then \(\det B=-\det A\).
c. If one row of A is multiplied by \(k\) to produce B, then \(\det B=k\cdot\det A\).

-

Also since \(\det A^T=\det A\), a row operation on \(A^T\) amounts to a column operation on \(A\). The above property is true for column operations as well.

With these properties in mind, we can do the following

\[\begin{align} @@ -403,7 +401,7 @@

Problem 3

Problem 3 Solution

-

Denote \(A=BCB^{-1}\), it can be seen that \[\det A=\det BCB^{-1}=\det B\det C\det B^{-1}=\det (BB^{-1})\det C=\det C\]

+

Denote \(A=BCB^{-1}\), it can be seen that \[\det A=\det (BCB^{-1})=\det B\det C\det B^{-1}=\det (BB^{-1})\det C=\det C\]

Thus we can directly write down the determinant calculation process like below (applying row operations) \[ \begin{vmatrix}1&2&3\\1&4&5\\-1&3&7\end{vmatrix}= \begin{vmatrix}1&2&3\\0&2&2\\0&5&10\end{vmatrix}= @@ -433,7 +431,7 @@

Problem 6

Note the trace of a square matrix \(A\) is the sum of the diagonal entries in A and is denoted by tr \(A\).

Remember the formula for inverse matrix \[ -A^{-1}=\frac{1}{\det A}\text{adj}\;A=[b_{ij}]\\ +A^{-1}=\frac{1}{\det A}\text{adj}\;A=[b_{ij}]\qquad b_{ij}=\frac{C_{ji}}{\det A}\qquad C_{ji}=(-1)^{i+j}\det A_{ji} \] Where \(\text{adj}\;A\) is the adjugate of \(A\), \(C_{ji}\) is a cofactor of \(A\), and \(A_{ji}\) denotes the submatrix of \(A\) formed by deleting row \(j\) and column \(i\).

Now we can find the answer step-by-step:

@@ -457,7 +455,37 @@

Problem 7

Problem 7 Solution

- +

First do row reduction to get row echelon form of the matrix \(A\):

+

\[\begin{align} +&\begin{bmatrix}1&2&2&10&3\\2&4&1&11&5\\3&6&2&18&1\end{bmatrix}\sim +\begin{bmatrix}1&2&2&10&3\\0&0&-3&-9&-1\\0&0&-4&-12&-8\end{bmatrix}\sim +\begin{bmatrix}1&2&2&10&3\\0&0&3&9&1\\0&0&1&3&2\end{bmatrix}\\ +\sim&\begin{bmatrix}1&2&2&10&3\\0&0&3&9&1\\0&0&3&9&6\end{bmatrix} +\sim\begin{bmatrix}\color{fuchsia}{1}&2&2&10&3\\0&0&\color{fuchsia}{3}&9&1\\0&0&0&0&\color{fuchsia}{5}\end{bmatrix} +\end{align}\]

+

This shows that there are 3 pivot elements and 3 corresponding pivot columns (from the original matrix \(A\)) shown below

+

\[\begin{Bmatrix} +\begin{bmatrix}1\\2\\3\end{bmatrix}, +\begin{bmatrix}2\\1\\2\end{bmatrix}, +\begin{bmatrix}3\\5\\1\end{bmatrix} +\end{Bmatrix}\]

+

These columns form a basis for \(\text{Col}\;A\). Now look at the statements A and E.

+

In the statement A, the first vector equals the sum of the first two pivot columns above. In the statement E, the third vector equals the sum of the last two pivot columns above. So both are TRUE.

+

To check the statements B, C, and D, we need to find the basis for \(\text{Nul}\;A\). From the row echelon form, it can be deduced that with \(x_2\) and \(x_4\) as free variable \[\begin{align} +x_5&=0\\x_3&=-3x_4\\x_1&=-2x_2-2x_3-10x_4=-2x_2-4x_4 +\end{align}\] This leads to \[ +\begin{bmatrix}x_1\\x_2\\x_3\\x_4\\x_5\end{bmatrix}= +\begin{bmatrix}-2x_2-4x_4\\x_2\\-3x_4\\x_4\\0\end{bmatrix}= +x_2\begin{bmatrix}-2\\1\\0\\0\\0\end{bmatrix}+x_4\begin{bmatrix}-4\\0\\-3\\1\\0\end{bmatrix} +\]

+

So the basis of \(\text{Nul}\;A\) is \[\begin{Bmatrix} +\begin{bmatrix}-2\\1\\0\\0\\0\end{bmatrix}, +\begin{bmatrix}-4\\0\\-3\\1\\0\end{bmatrix} +\end{Bmatrix}\]

+

The statement B is TRUE because its first vector is the first column above scaled by 2, and its 2nd vector is just the 2nd column above scaled by -1.

+

For statement D, its 1st vector is the same as the first column above, and the 2nd vector is just the sum of the two columns. It is TRUE as well.

+

The statement B is FALSE since generating the 2nd vector with 3 and -2 coexisting is impossible.

+

So the answer is C.

Problem 8

@@ -471,7 +499,10 @@

Problem 9

Problem 9 Solution

- +

To find the \(\text{Ker}(T)\), need to find the set of \(p(t)\) such that \(T(p(t))=0\) \[ +T(a_0+a_{1}t+a_{2}t^2)=a_{2}t^3=0 \Rightarrow a_2=0 +\] Thus \(p(t)=a_0+a_{1}t\), the basis is \({1,t}\).

+

So the answer is A.

Problem 10

@@ -485,21 +516,46 @@

Problem 11

Problem 11 Solution

- +

The vector set can be regarded as a linear transformation, then we can do row reduction with it:

+

\[ +\begin{bmatrix}1&1&1&1&1\\-1&1&2&0&-2\\1&1&1&1&3\end{bmatrix}\sim +\begin{bmatrix}\color{fuchsia}{1}&1&1&1&1\\0&\color{fuchsia}{2}&3&1&-1\\0&0&0&0&\color{fuchsia}{2}\end{bmatrix} +\] So there are 3 pivot entries and the rank is 3. The pivot columns below form a basis for \(H\). \[\begin{Bmatrix} +\begin{bmatrix}1\\-1\\1\end{bmatrix}, +\begin{bmatrix}1\\1\\1\end{bmatrix}, +\begin{bmatrix}1\\-2\\3\end{bmatrix} +\end{Bmatrix}\]

+

A is wrong as it has only 2 vectors and the rank is 2.

+

For B, C, and D, their 3rd vectors can be generated with the linear combination of the first two vectors. So their ranks are also 2.

+

E is equivalent to the basis above. Its second vector can be generated like below \[ +\begin{bmatrix}1\\-1\\1\end{bmatrix}+\begin{bmatrix}1\\1\\1\end{bmatrix}= +\begin{bmatrix}2\\0\\2\end{bmatrix}=2\times \begin{bmatrix}1\\0\\1\end{bmatrix} +\]

+

So the answer is E.

Problem 12

Problem 12 Solution

- +

Note this question asks which one is NOT in the subspace spanned by \(\pmb x\) and \(\pmb y\). A vector is in the subspace spanned by \(\pmb x\) and \(\pmb y\) if and only if it is a linear combination of \(\pmb x\) and \(\pmb y\). This also means that the augmented matrix \([\pmb x\;\pmb y \mid \pmb v]\) has solutions.

+

Let's try vector from A. \[ +\left[\begin{array}{cc|c}2&1&4\\3&2&2\\1&1&1\end{array}\right]\sim +\left[\begin{array}{cc|c}2&1&4\\3&2&2\\2&2&2\end{array}\right]\sim +\left[\begin{array}{cc|c}2&1&4\\1&0&0\\0&1&-2\end{array}\right]\sim +\left[\begin{array}{cc|c}2&0&6\\1&0&0\\0&1&-2\end{array}\right]\sim +\] This gives inconsistent results for \(x_1\). This vector is NOT a linear combination of \(\pmb x\) and \(\pmb y\). We do not need to continue here.

+

So the answer is A.

Problem 13

Problem 13 Solution

- +

For 2 radians counter-clockwise rotation, the transformation matrix is written as \[A=\begin{bmatrix}\cos(2)&-\sin(2)\\\sin(2)&\cos(2)\end{bmatrix}\] To find the eigenvalues of this \(2\times 2\) matrix, need to solve the equation \(\det (A-\lambda I)=0\) \[ +\begin{vmatrix}\cos(2)-\lambda&\sin(2)\\-\sin(2)&\cos(2)-\lambda\end{vmatrix}=\lambda^2-2\cos(2)+\cos^2(2)+\sin^2(2)=\lambda^2-2\cos(2)+1 +\] Apply the quadratic formula, get the roots \[\lambda=\frac{2\cos(2)\pm\sqrt{4\cos^2(2)-4}}{2}=\cos(2)\pm i\sin(2)\]

+

So the answer is C.

Problem 14

@@ -534,7 +590,48 @@

Problem 18

Problem 18 Solution

- +

Remember Problem 6 introduced the definition of trace, which is the sum of all diagonal entries of a matrix. Denote the \(2\times 2\) as \(A=\begin{bmatrix}a&b\\c&d\end{bmatrix}\), then \(\text{tr}(A)=a+d=-2\). Since \(\det A=11\), it gives \(ad-bc=11\).

+

With these in mind, we can do the eigenvalue calculation below \[ +\begin{vmatrix}a-\lambda&b\\c&d-\lambda\end{vmatrix}=\lambda^2-(a+d)\lambda+ad-bc=\lambda^2+2\lambda+11=0 +\] Apply the quadratic formula, get the roots \[\lambda=\frac{-2\pm\sqrt{4-44}}{2}=-1\pm i\sqrt{10}\]

+
+

Refer to the following table for the mapping from \(2\times 2\) matrix eigenvalues to trajectories:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EigenvaluesTrajectories
\(\lambda_1>0, \lambda_2>0\)Repeller/Source
\(\lambda_1<0, \lambda_2<0\)Attactor/Sink
\(\lambda_1<0, \lambda_2>0\)Saddle Point
\(\lambda = a\pm bi, a>0\)Spiral (outward) Point
\(\lambda = a\pm bi, a<0\)Spiral (inward) Point
\(\lambda = \pm bi\)Ellipses (circles if \(b=1\))
+
+

So the answer is C.

Problem 19

@@ -694,14 +791,14 @@

Other MA265 Final Exam Solutions

Symbols count total: - 436k + 441k
diff --git a/archives/2021/12/index.html b/archives/2021/12/index.html index d1520e5..89792bd 100644 --- a/archives/2021/12/index.html +++ b/archives/2021/12/index.html @@ -333,14 +333,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2021/index.html b/archives/2021/index.html index a8b726d..bdab4d9 100644 --- a/archives/2021/index.html +++ b/archives/2021/index.html @@ -333,14 +333,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2022/01/index.html b/archives/2022/01/index.html index 1b8f275..f73f28d 100644 --- a/archives/2022/01/index.html +++ b/archives/2022/01/index.html @@ -293,14 +293,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2022/03/index.html b/archives/2022/03/index.html index a711245..62b7d18 100644 --- a/archives/2022/03/index.html +++ b/archives/2022/03/index.html @@ -293,14 +293,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2022/04/index.html b/archives/2022/04/index.html index 93bceaf..455af5a 100644 --- a/archives/2022/04/index.html +++ b/archives/2022/04/index.html @@ -293,14 +293,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2022/08/index.html b/archives/2022/08/index.html index 8117049..0dfd5a9 100644 --- a/archives/2022/08/index.html +++ b/archives/2022/08/index.html @@ -293,14 +293,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2022/11/index.html b/archives/2022/11/index.html index 1029483..8413c1c 100644 --- a/archives/2022/11/index.html +++ b/archives/2022/11/index.html @@ -313,14 +313,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2022/index.html b/archives/2022/index.html index 2dfbdbe..092ca2a 100644 --- a/archives/2022/index.html +++ b/archives/2022/index.html @@ -393,14 +393,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2023/03/index.html b/archives/2023/03/index.html index e4768a5..9ecd2e9 100644 --- a/archives/2023/03/index.html +++ b/archives/2023/03/index.html @@ -313,14 +313,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2023/08/index.html b/archives/2023/08/index.html index 8fc3f19..bb7736d 100644 --- a/archives/2023/08/index.html +++ b/archives/2023/08/index.html @@ -293,14 +293,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2023/11/index.html b/archives/2023/11/index.html index 17c4abc..7905861 100644 --- a/archives/2023/11/index.html +++ b/archives/2023/11/index.html @@ -313,14 +313,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2023/index.html b/archives/2023/index.html index 2befa0e..23e68fd 100644 --- a/archives/2023/index.html +++ b/archives/2023/index.html @@ -373,14 +373,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2024/01/index.html b/archives/2024/01/index.html index a683d7e..6a777fa 100644 --- a/archives/2024/01/index.html +++ b/archives/2024/01/index.html @@ -333,14 +333,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2024/02/index.html b/archives/2024/02/index.html index 1ecf00e..a28cbfe 100644 --- a/archives/2024/02/index.html +++ b/archives/2024/02/index.html @@ -393,14 +393,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2024/03/index.html b/archives/2024/03/index.html index d21d960..79f5bfc 100644 --- a/archives/2024/03/index.html +++ b/archives/2024/03/index.html @@ -333,14 +333,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2024/04/index.html b/archives/2024/04/index.html index e60bf3c..72cf589 100644 --- a/archives/2024/04/index.html +++ b/archives/2024/04/index.html @@ -293,14 +293,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2024/index.html b/archives/2024/index.html index bd3a809..979faeb 100644 --- a/archives/2024/index.html +++ b/archives/2024/index.html @@ -476,14 +476,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/2024/page/2/index.html b/archives/2024/page/2/index.html index f246faa..111ea40 100644 --- a/archives/2024/page/2/index.html +++ b/archives/2024/page/2/index.html @@ -336,14 +336,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/index.html b/archives/index.html index b5dbcd9..4d6229f 100644 --- a/archives/index.html +++ b/archives/index.html @@ -476,14 +476,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/page/2/index.html b/archives/page/2/index.html index 66fc243..61a1643 100644 --- a/archives/page/2/index.html +++ b/archives/page/2/index.html @@ -482,14 +482,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/archives/page/3/index.html b/archives/page/3/index.html index d6e66d3..ba5070d 100644 --- a/archives/page/3/index.html +++ b/archives/page/3/index.html @@ -419,14 +419,14 @@

PacketMania

Symbols count total: - 436k + 441k
diff --git a/atom.xml b/atom.xml index 5e3d39e..22df34d 100644 --- a/atom.xml +++ b/atom.xml @@ -6,7 +6,7 @@ - 2024-04-26T07:14:35.052Z + 2024-04-27T02:37:29.460Z https://www.packetmania.net/en/ @@ -21,7 +21,7 @@ https://www.packetmania.net/en/2024/04/18/Purdue-MA265-2022-Spring-Final/ 2024-04-19T06:51:21.000Z - 2024-04-26T07:14:35.052Z + 2024-04-27T02:37:29.460Z <p>Here comes the solution and analysis for Purdue MA 26500 Spring 2022 Final exam. This exam covers all topics from Chapter 1 (Linear Equations in Linear Algebra) to Chapter 7 Section 1 (Diagonalization of Symmetric Matrices). @@ -370,10 +370,10 @@ - - + + diff --git a/categories/DIY-Projects/index.html b/categories/DIY-Projects/index.html index 19fa51a..4e9dff4 100644 --- a/categories/DIY-Projects/index.html +++ b/categories/DIY-Projects/index.html @@ -294,14 +294,14 @@

DIY Projects Symbols count total: - 436k + 441k

diff --git a/categories/Study-Notes/index.html b/categories/Study-Notes/index.html index 4528304..742f8ef 100644 --- a/categories/Study-Notes/index.html +++ b/categories/Study-Notes/index.html @@ -477,14 +477,14 @@

Study Notes Symbols count total: - 436k + 441k

diff --git a/categories/Study-Notes/page/2/index.html b/categories/Study-Notes/page/2/index.html index 0486522..60c2e44 100644 --- a/categories/Study-Notes/page/2/index.html +++ b/categories/Study-Notes/page/2/index.html @@ -446,14 +446,14 @@

Study Notes Symbols count total: - 436k + 441k

diff --git a/categories/Technical-Know-how/index.html b/categories/Technical-Know-how/index.html index e0da071..97ed47b 100644 --- a/categories/Technical-Know-how/index.html +++ b/categories/Technical-Know-how/index.html @@ -397,14 +397,14 @@

Technical Know-how Symbols count total: - 436k + 441k

diff --git a/categories/Technology-Review/index.html b/categories/Technology-Review/index.html index 5604ca9..fd83878 100644 --- a/categories/Technology-Review/index.html +++ b/categories/Technology-Review/index.html @@ -294,14 +294,14 @@

Technology Review Symbols count total: - 436k + 441k

diff --git a/categories/Tool-Guide/index.html b/categories/Tool-Guide/index.html index 9e598a6..2d69da4 100644 --- a/categories/Tool-Guide/index.html +++ b/categories/Tool-Guide/index.html @@ -294,14 +294,14 @@

Tool Guide Symbols count total: - 436k + 441k

diff --git a/categories/index.html b/categories/index.html index b8b91c9..8438e4a 100644 --- a/categories/index.html +++ b/categories/index.html @@ -289,14 +289,14 @@

Categories Symbols count total: - 436k + 441k

diff --git a/index.html b/index.html index 77de1b1..1be2388 100644 --- a/index.html +++ b/index.html @@ -584,7 +584,7 @@

- +

@@ -978,14 +978,14 @@

Symbols count total: - 436k + 441k
diff --git a/page/2/index.html b/page/2/index.html index b49b0d3..fcfd59e 100644 --- a/page/2/index.html +++ b/page/2/index.html @@ -969,14 +969,14 @@

Symbols count total: - 436k + 441k

diff --git a/page/3/index.html b/page/3/index.html index 6e7f492..6e83b94 100644 --- a/page/3/index.html +++ b/page/3/index.html @@ -969,14 +969,14 @@

Symbols count total: - 436k + 441k

diff --git a/page/4/index.html b/page/4/index.html index 5b10bf8..daf7a1d 100644 --- a/page/4/index.html +++ b/page/4/index.html @@ -867,14 +867,14 @@

Symbols count total: - 436k + 441k

diff --git a/search.json b/search.json index 46e2f1c..0e2b575 100644 --- a/search.json +++ b/search.json @@ -1 +1 @@ -[{"title":"AddressSanitizer - A Tool for Programmers to Detect Memory Access Errors","url":"/en/2022/04/22/ASAN-intro/","content":"

Memory access errors are the most common software errors that often cause program crashes. The AddressSanitizer tool, developed by Google engineers in 2012, has become the first choice of C/C++ programmers for its wide coverage, high efficiency, and low overhead. Here is a brief introduction to its principle and usage.

\n

One man's \"magic\" is another man's engineering. \"Supernatural\" is a null word.
Robert Anson Heinlein (American science fiction author, aeronautical engineer, and naval officer)

\n
\n

Tool Overview

\n

The C/C++ language allows programmers to have low-level control over memory, and this direct memory management has made it possible to write efficient application software. However, this has also made memory access errors, including buffer overflows, accesses to freed memory, and memory leaks, a serious problem that must be coped with in program design and implementation. While there are tools and software that provide the ability to detect such errors, their operational efficiency, and functional coverage are often less than ideal.

\n

In 2012, Google engineer Konstantin Serebryany and team members released an open-source memory access error detector for C/C++ programs called AddressSanitizer1. AddressSanitizer (ASan) applies new memory allocation, mapping, and code stubbing techniques to detect almost all memory access errors efficiently. Using the SPEC 2006 benchmark analysis package, ASan runs with an average slowdown of less than 2 and memory consumption of about 2.4 times. In comparison, another well-known detection tool Valgrind has an average slowdown of 20, which makes it almost impossible to put into practice.

\n

The following table summarizes the types of memory access errors that ASan can detect for C/C++ programs:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Error TypeAbbreviationNotes
heap use after freeUAFAccess freed memory (dangling pointer dereference)
heap buffer overflowHeap OOBDynamic allocated memory out-of-bound read/write
heap memory leakHMLDynamic allocated memory not freed after use
global buffer overflowGlobal OOBGlobal object out-of-bound read/write
stack use after scopeUASLocal object out-of-scope access
stack use after returnUARLocal object out-of-scope access after return
stack buffer overflowStack OOBLocal object out-of-bound read/write
\n

ASan itself cannot detect heap memory leaks. But when ASan is integrated into the compiler, as it replaces the memory allocation/free functions, the original leak detection feature of the compiler tool is consolidated with ASan. So, adding the ASan option to the compilation command line also turns on the leak detection feature by default.

\n
\n

This covers all common memory access errors except for \"uninitialized memory reads\" (UMR). ASan detects them with a false positive rate of 0, which is quite impressive. In addition, ASan detects several C++-specific memory access errors such as

\n
    \n
  • Initialization Order Fiasco: When two static objects are defined in different source files and the constructor of one object calls the method of the other object, a program crash will occur if the former compilation unit is initialized first.
  • \n
  • Container Overflow: Given libc++/libstdc++ container, access [container.end(), container.begin() + container.capacity())], which crosses the [container.begin(), container.end()] range but still within the dynamically allocated memory area.
  • \n
  • Delete Mismatch: For the array object created by new foo[n], should not call delete foo for deletion, use delete [] foo instead.
  • \n
\n

ASan's high reliability and performance have made it the preferred choice of compiler and IDE developers since its introduction. Today ASan is integrated into all four major compilation toolsets:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Compiler/IDEFirst Support VersionOSPlatform
Clang/LLVM23.1Unix-likeCross-platform
GCC4.8Unix-likeCross-platform
Xcode7.0Mac OS XApple products
MSVC16.9WindowsIA-32, x86-64 and ARM
\n

ASan's developers first used the Chromium open-source browser for routine testing and found more than 300 memory access errors over 10 months. After integration into mainstream compilation tools, it reported long-hidden bugs in numerous popular open-source software, such as Mozilla Firefox, Perl, Vim, PHP, and MySQL. Interestingly, ASan also identified some memory access errors in the LLVM and GCC compilers' code. Now, many software companies have added ASan run to their mandatory quality control processes.

\n

Working Principle

\n

The USENIX conference paper 3, published by Serebryany in 2012, comprehensively describes the design principles, algorithmic ideas, and programming implementation of ASan. In terms of the overall structure, ASan consists of two parts.

\n
    \n
  1. Compiler instrumentation - modifies the code to verify the shadow memory state at each memory access and creates poisoned red zones at the edges of global and stack objects to detect overflows or underflows.
  2. \n
  3. Runtime library replacement - replaces malloc/free and its related functions to create poisoned red zones at the edge of dynamically allocated heap memory regions, delay the reuse of memory regions after release, and generate error reports.
  4. \n
\n

Here shadow memory, compiler instrumentation, and memory allocation function replacement are all previously available techniques, so how has ASan innovatively applied them for efficient error detection? Let's take a look at the details.

\n

Shadow Memory

\n

Many inspection tools use separated shadow memory to record metadata about program memory, and then apply instrumentation to check the shadow memory during memory accesses to confirm that reads and writes are safe. The difference is that ASan uses a more efficient direct mapping shadow memory.

\n

The designers of ASan noted that typically the malloc function returns a memory address that is at least 8-byte aligned. For example, a request for 20 bytes of memory would divide 24 bytes of memory, with the last 3 bits of the actual return pointer being all zeros. in addition, any aligned 8-byte sequence would only have 9 different states: the first \\(k\\,(0\\leq k \\leq 8)\\) bytes are accessible, and the last \\(8-k\\) are not. From this, they came up with a more compact shadow memory mapping and usage scheme:

\n
    \n
  • Reserve one-eighth of the virtual address space for shadow memory
  • \n
  • Directly map application memory to shadow memory using a formula that divides by 8 plus an offset\n
      \n
    • 32-bit application: Shadow = (Mem >> 3) + 0x20000000;
    • \n
    • 64-bit application: Shadow = (Mem >> 3) + 0x7fff8000;
    • \n
  • \n
  • Each byte of shadow memory records one of the 9 states of the corresponding 8-byte memory block\n
      \n
    • 0 means all 8 bytes are addressable
    • \n
    • Any negative value indicates that the entire 8-byte word is unaddressable (poisoned )
    • \n
    • k (1 ≤ k ≤ 7) means that the first k bytes are addressable
    • \n
  • \n
\n

The following figure shows the address space layout and mapping relationship of ASan. Pay attention to the Bad area in the middle, which is the address segment after the shadow memory itself is mapped. Because shadow memory is not visible to the application, ASan uses a page protection mechanism to make it inaccessible.

\n

\n

Compiler Instrumentation

\n

Once the shadow memory design is determined, the implementation of compiler instrumentation to detect dynamic memory access errors is easy. For memory accesses of 8 bytes, the shadow memory bytes are checked by inserting instructions before the original read/write code, and an error is reported if they are not zero. For memory accesses of less than 8 bytes, the instrumentation is a bit more complicated, where the shadow memory byte values are compared with the last three bits of the read/write address. This situation is also known as the \"slow path\" and the sample code is as follows.

\n
// Check the cases where we access first k bytes of the qword
// and these k bytes are unpoisoned.
bool SlowPathCheck(shadow_value, address, kAccessSize) {
last_accessed_byte = (address & 7) + kAccessSize - 1;
return (last_accessed_byte >= shadow_value);
}
...

byte *shadow_address = MemToShadow(address);
byte shadow_value = *shadow_address;
if (shadow_value) {
if (SlowPathCheck(shadow_value, address, kAccessSize)) {
ReportError(address, kAccessSize, kIsWrite);
}
}
*address = ...; // or: ... = *address;
\n

For global and stack (local) objects, ASan has designed different instrumentation to detect their out-of-bounds access errors. The red zone around a global object is added by the compiler at compile time and its address is passed to the runtime library at application startup, where the runtime library function then poisons the red zone and writes down the address needed in error reporting. The stack object is created at function call time, and accordingly, its red zone is created and poisoned at runtime. In addition, because the stack object is deleted when the function returns, the instrumentation code must also zero out the shadow memory it is mapped to.

\n

In practice, the ASan compiler instrumentation process is placed at the end of the compiler optimization pipeline so that instrumentation only applies to the remaining memory access instructions after variable and loop optimization. In the latest GCC distribution, the ASan compiler stubbing code is located in two files in the gcc subdirectory gcc/asan.[ch].

\n

Runtime Library Replacement

\n

The runtime library needs to include code to manage shadow memory. The address segment to which shadow memory itself is mapped is to be initialized at application startup to disable access to shadow memory by other parts of the program. The runtime library replaces the old memory allocation and free functions and also adds some error reporting functions such as __asan_report_load8.

\n

The newly replaced memory allocation function malloc will allocate additional storage as a red zone before and after the requested memory block and set the red zone to be non-addressable. This is called the poisoning process. In practice, because the memory allocator maintains a list of available memory corresponding to different object sizes, if the list of a certain object is empty, the OS will allocate a large set of memory blocks and their red zones at once. As a result, the red zones of the preceding and following memory blocks will be connected, as shown in the following figure, where \\(n\\) memory blocks require only \\(n+1\\) red zones to be allocated.

\n

\n

The new free function needs to poison the entire storage area and place it in a quarantine queue after the memory is freed. This prevents the memory region from being allocated any time soon. Otherwise, if the memory region is reused immediately, there is no way to detect incorrect accesses to the recently freed memory. The size of the quarantine queue determines how long the memory region is in quarantine, and the larger it is the better its capability of detecting UAF errors!

\n

By default, both the malloc and free functions log their call stacks to provide more detailed information in the error reports. The call stack for malloc is kept in the red zone to the left of the allocated memory, so a large red zone can retain more call stack frames. The call stack for free is stored at the beginning of the allocated memory region itself.

\n

Integrated into the GCC compiler, the source code for the ASan runtime library replacement is located in the libsanitizer subdirectory libsanitizer/asan/*, and the resulting runtime library is compiled as libasan.so.

\n

Application Examples

\n

ASan is very easy to use. The following is an example of an Ubuntu Linux 20.4 + GCC 9.3.0 system running on an x86_64 virtual machine to demonstrate the ability to detect various memory access errors.

\n

Test Cases

\n

As shown below, the test program writes seven functions, each introducing a different error type. The function names are cross-referenced with the error types one by one:

\n
/*
* PakcteMania https://www.packetmania.net
*
* gcc asan-test.c -o asan-test -fsanitize=address -g
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
/* #include <sanitizer/lsan_interface.h> */

int ga[10] = {1};

int global_buffer_overflow() {
return ga[10];
}

void heap_leak() {
int* k = (int *)malloc(10*sizeof(int));
return;
}

int heap_use_after_free() {
int* u = (int *)malloc(10*sizeof(int));
u[9] = 10;
free(u);
return u[9];
}

int heap_buffer_overflow() {
int* h = (int *)malloc(10*sizeof(int));
h[0] = 10;
return h[10];
}

int stack_buffer_overflow() {
int s[10];
s[0] = 10;
return s[10];
}

int *gp;

void stack_use_after_return() {
int r[10];
r[0] = 10;
gp = &r[0];
return;
}

void stack_use_after_scope() {
{
int c = 0;
gp = &c;
}
*gp = 10;
return;
}
\n

The test program calls the getopt library function to support a single-letter command line option that allows the user to select the type of error to be tested. The command line option usage information is as follows.

\n
\b$ ./asan-test

Test AddressSanitizer
usage: asan-test [ -bfloprs ]

-b\theap buffer overflow
-f\theap use after free
-l\theap memory leak
-o\tglobal buffer overflow
-p\tstack use after scope
-r\tstack use after return
-s\tstack buffer overflow
\n

The GCC compile command for the test program is simple, just add two compile options

\n
    \n
  • -fsanitize=address: activates the ASan tool
  • \n
  • -g: enable debugging and keep debugging information
  • \n
\n

OOB Test

\n

For Heap OOB error, the run result is

\n
$ ./asan-test -b
=================================================================
==57360==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x604000000038 at pc 0x55bf46fd64ed bp 0x7ffced908dc0 sp 0x7ffced908db0
READ of size 4 at 0x604000000038 thread T0
#0 0x55bf46fd64ec in heap_buffer_overflow /home/zixi/coding/asan-test.c:34
#1 0x55bf46fd6a3f in main /home/zixi/coding/asan-test.c:88
#2 0x7fd16f6560b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x55bf46fd628d in _start (/home/zixi/coding/asan-test+0x128d)

0x604000000038 is located 0 bytes to the right of 40-byte region [0x604000000010,0x604000000038)
allocated by thread T0 here:
#0 0x7fd16f92ebc8 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
#1 0x55bf46fd646c in heap_buffer_overflow /home/zixi/coding/asan-test.c:32
#2 0x55bf46fd6a3f in main /home/zixi/coding/asan-test.c:88
#3 0x7fd16f6560b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/zixi/coding/asan-test.c:34 in heap_buffer_overflow
Shadow bytes around the buggy address:
0x0c087fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c087fff8000: fa fa 00 00 00 00 00[fa]fa fa fa fa fa fa fa fa
0x0c087fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
...
==57360==ABORTING
\n

Referring to the heap-buffer-overflow function implementation, you can see that it requests 40 bytes of memory to hold 10 32-bit integers. However, on the return of the function, the code overruns to read the data after the allocated memory. As the above run log shows, the program detects a Heap OOB error and aborts immediately. ASan reports the name of the source file and line number asan-test.c:34 where the error occurred, and also accurately lists the original allocation function call stack for dynamically allocated memory. The \"SUMMARY\" section of the report also prints the shadow memory data corresponding to the address in question (observe the lines marked by =>). The address to be read is 0x604000000038, whose mapped shadow memory address 0x0c087fff8007 holds the negative value 0xfa (poisoned and not addressable). Because of this, ASan reports an error and aborts the program.

\n

The Stack OOB test case is shown below. ASan reports an out-of-bounds read error for a local object. Since the local variables are located in the stack space, the starting line number asan-test.c:37 of the function stack_buffr_overflow is listed. Unlike the Heap OOB report, the shadow memory poisoning values for the front and back redzone of the local variable are different, with the previous Stack left redzone being 0xf1 and the later Stack right redzone being 0xf3. Using different poisoning values (both negative after 0x80) helps to quickly distinguish between the different error types.

\n
$ ./asan-test -s
=================================================================
==57370==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7f1cf5044058 at pc 0x55d8b7e9d601 bp 0x7ffc830c29e0 sp 0x7ffc830c29d0
READ of size 4 at 0x7f1cf5044058 thread T0
#0 0x55d8b7e9d600 in stack_buffer_overflow /home/zixi/coding/asan-test.c:40
#1 0x55d8b7e9daec in main /home/zixi/coding/asan-test.c:108
#2 0x7f1cf87760b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x55d8b7e9d28d in _start (/home/zixi/coding/asan-test+0x128d)

Address 0x7f1cf5044058 is located in stack of thread T0 at offset 88 in frame
#0 0x55d8b7e9d505 in stack_buffer_overflow /home/zixi/coding/asan-test.c:37

This frame has 1 object(s):
[48, 88) 's' (line 38) <== Memory access at offset 88 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/zixi/coding/asan-test.c:40 in stack_buffer_overflow
Shadow bytes around the buggy address:
0x0fe41ea007b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea007c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea007d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea007e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea007f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0fe41ea00800: f1 f1 f1 f1 f1 f1 00 00 00 00 00[f3]f3 f3 f3 f3
0x0fe41ea00810: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea00820: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea00830: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea00840: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea00850: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
...
==57370==ABORTING
\n

The following Global OOB test result also clearly shows the error line asan-test.c:16, the global variable name ga and its definition code location asan-test.c:13:5, and you can also see that the global object has a red zone poisoning value of 0xf9.

\n
$ ./asan-test -o
=================================================================
==57367==ERROR: AddressSanitizer: global-buffer-overflow on address 0x564363ea4048 at pc 0x564363ea1383 bp 0x7ffc0d6085d0 sp 0x7ffc0d6085c0
READ of size 4 at 0x564363ea4048 thread T0
#0 0x564363ea1382 in global_buffer_overflow /home/zixi/coding/asan-test.c:16
#1 0x564363ea1a6c in main /home/zixi/coding/asan-test.c:98
#2 0x7f8cb43890b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x564363ea128d in _start (/home/zixi/coding/asan-test+0x128d)

0x564363ea4048 is located 0 bytes to the right of global variable 'ga' defined in 'asan-test.c:13:5' (0x564363ea4020) of size 40
SUMMARY: AddressSanitizer: global-buffer-overflow /home/zixi/coding/asan-test.c:16 in global_buffer_overflow
Shadow bytes around the buggy address:
0x0ac8ec7cc7b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc7c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc7d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc7e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc7f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0ac8ec7cc800: 00 00 00 00 00 00 00 00 00[f9]f9 f9 f9 f9 f9 f9
0x0ac8ec7cc810: 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
0x0ac8ec7cc820: f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
0x0ac8ec7cc830: f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 00 00 00 00
0x0ac8ec7cc840: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc850: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
...
==57367==ABORTING
\n

Note that in this example, the global array int ga[10] = {1}; is initialized, what happens if it is uninitialized? Change the code slightly

\n
int ga[10];

int global_buffer_overflow() {
ga[0] = 10;
return ga[10];
}
\n

Surprisingly, ASan does not report the obvious Global OOB error here. Why?

\n

The reason has to do with the way GCC treats global variables. The compiler treats functions and initialized variables as Strong symbols, while uninitialized variables are Weak symbols by default. Since the definition of weak symbols may vary from source file to source file, the size of the space required is unknown. The compiler cannot allocate space for weak symbols in the BSS segment, so it uses the COMMON block mechanism so that all weak symbols share a COMMON memory region, thus ASan cannot insert the red zone. During the linking process, after the linker reads all the input target files, it can determine the size of the weak symbols and allocate space for them in the BSS segment of the final output file.

\n

Fortunately, GCC's -fno-common option turns off the COMMON block mechanism, allowing the compiler to add all uninitialized global variables directly to the BSS segment of the target file, also allowing ASan to work properly. This option also disables the linker from merging weak symbols, so the linker reports an error directly when it finds a compiled unit with duplicate global variables defined in the target file.

\n

This is confirmed by a real test. Modify the GCC command line for the previous code segment

\n
gcc asan-test.c -o asan-test -fsanitize=address -fno-common -g
\n

then compile, link, and run. ASan successfully reported the Global OOB error.

\n

UAF Test

\n

The following is a running record of UAF error detection. Not only is the information about the code that went wrong reported here, but also the call stack of the original allocation and free functions of the dynamic memory is given. The log shows that the memory was allocated by asan-test.c:25, freed at asan-test.c:27, and yet read at asan-test.c:28. The shadow memory data printed later indicates that the data filled is negative 0xfd, which is also the result of the poisoning of the memory after it is freed.

\n
$ \u0007./asan-test -\bf
=================================================================
==57363==ERROR: AddressSanitizer: heap-use-after-free on address 0x604000000034 at pc 0x558b4a45444e bp 0x7ffccf4ca790 sp 0x7ffccf4ca780
READ of size 4 at 0x604000000034 thread T0
#0 0x558b4a45444d in heap_use_after_free /home/zixi/coding/asan-test.c:28
#1 0x558b4a454a4e in main /home/zixi/coding/asan-test.c:91
#2 0x7fc7cc98b0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x558b4a45428d in _start (/home/zixi/coding/asan-test+0x128d)

0x604000000034 is located 36 bytes inside of 40-byte region [0x604000000010,0x604000000038)
freed by thread T0 here:
#0 0x7fc7ccc637cf in __interceptor_free (/lib/x86_64-linux-gnu/libasan.so.5+0x10d7cf)
#1 0x558b4a454412 in heap_use_after_free /home/zixi/coding/asan-test.c:27
#2 0x558b4a454a4e in main /home/zixi/coding/asan-test.c:91
#3 0x7fc7cc98b0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

previously allocated by thread T0 here:
#0 0x7fc7ccc63bc8 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
#1 0x558b4a4543bd in heap_use_after_free /home/zixi/coding/asan-test.c:25
#2 0x558b4a454a4e in main /home/zixi/coding/asan-test.c:91
#3 0x7fc7cc98b0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

SUMMARY: AddressSanitizer: heap-use-after-free /home/zixi/coding/asan-test.c:28 in heap_use_after_free
Shadow bytes around the buggy address:
0x0c087fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c087fff8000: fa fa fd fd fd fd[fd]fa fa fa fa fa fa fa fa fa
0x0c087fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
...
==57363==ABORTING
\n

HML Test

\n

The results of the memory leak test are as follows. Unlike the other test cases, ABORTING is not printed at the end of the output record. This is because, by default, ASan only generates a memory leak report when the program terminates (process ends). If you want to check for leaks on the fly, you can call ASan's library function __lsan_do_recoverable_leak_check, whose definition is located in the header file sanitizer/lsan_interface.h.

\n
$ ./asan-test -l
=================================================================
==57365==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 40 byte(s) in 1 object(s) allocated from:
#0 0x7f06b85b1bc8 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
#1 0x5574a8bcd3a0 in heap_leak /home/zixi/coding/asan-test.c:20
#2 0x5574a8bcda5d in main /home/zixi/coding/asan-test.c:94
#3 0x7f06b82d90b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

SUMMARY: AddressSanitizer: 40 byte(s) leaked in 1 allocation(s).
\n

UAS Test

\n

See the stack_use_after_scope function code, where the memory unit holding the local variable c is written outside of its scope. The test log accurately reports the line number line 54 where the variable is defined and the location of the incorrect writing code asan-test.c:57:

\n
./asan-test -\bp
=================================================================
==57368==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7f06f0a9b020 at pc 0x56121a7548d9 bp 0x7ffd1de0d050 sp 0x7ffd1de0d040
WRITE of size 4 at 0x7f06f0a9b020 thread T0
#0 0x56121a7548d8 in stack_use_after_scope /home/zixi/coding/asan-test.c:57
#1 0x56121a754a7b in main /home/zixi/coding/asan-test.c:101
#2 0x7f06f42cd0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x56121a75428d in _start (/home/zixi/coding/asan-test+0x128d)

Address 0x7f06f0a9b020 is located in stack of thread T0 at offset 32 in frame
#0 0x56121a7547d0 in stack_use_after_scope /home/zixi/coding/asan-test.c:52

This frame has 1 object(s):
[32, 36) 'c' (line 54) <== Memory access at offset 32 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope /home/zixi/coding/asan-test.c:57 in stack_use_after_scope
Shadow bytes around the buggy address:
0x0fe15e14b5b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b5c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b5d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b5e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b5f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0fe15e14b600: f1 f1 f1 f1[f8]f3 f3 f3 00 00 00 00 00 00 00 00
0x0fe15e14b610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b630: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
...
==57368==ABORTING
\n

UAR Test

\n

The UAR test has its peculiarities. Because the stack memory of a function is reused immediately after it returns, to detect local object access errors after return, a \"pseudo-stack\" of dynamic memory allocation must be set up, for details check the relevant Wiki page of ASan4. Since this algorithm change has some performance impact, ASan does not detect UAR errors by default. If you really need to, you can set the environment variable ASAN_OPTIONS to detect_stack_use_after_return=1 before running. The corresponding test logs are as follows.

\n
$ export ASAN_OPTIONS=detect_stack_use_after_return=1
$ env | grep ASAN
ASAN_OPTIONS=detect_stack_use_after_return=1
$ ./asan-test -\br
=================================================================
==57369==ERROR: AddressSanitizer: stack-use-after-return on address 0x7f5493e93030 at pc 0x55a356890ac9 bp 0x7ffd22c5cf30 sp 0x7ffd22c5cf20
READ of size 4 at 0x7f5493e93030 thread T0
#0 0x55a356890ac8 in main /home/zixi/coding/asan-test.c:105
#1 0x7f54975c50b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#2 0x55a35689028d in _start (/home/zixi/coding/asan-test+0x128d)

Address 0x7f5493e93030 is located in stack of thread T0 at offset 48 in frame
#0 0x55a356890682 in stack_use_after_return /home/zixi/coding/asan-test.c:45

This frame has 1 object(s):
[48, 88) 'r' (line 46) <== Memory access at offset 48 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-return /home/zixi/coding/asan-test.c:105 in main
Shadow bytes around the buggy address:
0x0feb127ca5b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca5c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca5d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca5e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca5f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0feb127ca600: f5 f5 f5 f5 f5 f5[f5]f5 f5 f5 f5 f5 f5 f5 f5 f5
0x0feb127ca610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca630: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
...
==57369==ABORTING
\n

ASan supports many other compiler flags and runtime environment variable options to control and tune the functionality and scope of the tests. For those interested please refer to the ASan flags Wiki page5.

\n

A zip archive of the complete test program is available for download here: asan-test.c.gz

\n
\n
\n
    \n
  1. AddressSanitizer Wiki↩︎

  2. \n
  3. Clang 13 documentation: ADDRESSSANITIZER↩︎

  4. \n
  5. Serebryany, K.; Bruening, D.; Potapenko, A.; Vyukov, D. \"AddressSanitizer: a fast address sanity checker\". In USENIX ATC, 2012↩︎

  6. \n
  7. AddressSanitizerUseAfterReturn↩︎

  8. \n
  9. AddressSanitizerFlags↩︎

  10. \n
\n
\n","categories":["Tool Guide"],"tags":["C/C++ Programming","System Programming"]},{"title":"Programming in C Exam Review and Practices (I)","url":"/en/2024/02/28/C-Prog-Exam-Review-Practices-1/","content":"

Here is a series of general study guides to college-level C programming courses. This is the first part covering compilation and linking, file operations, typedef, structures, string operations, basic pointer operations, etc.

\n

Compilation and Linking

\n
    \n
  • Write the command to compile a single C file named \"hello.c\" into an object file called \"hello.o\".

    \n

    gcc -c hello.c -o hello.o

  • \n
  • Write the command to link two object files named \"hello.o\" and \"goodbye.o\" into the executable called \"application\".

    \n

    gcc hello.o goodbye.o -o application

  • \n
  • Can you \"run\" an object file if it contains the \"main()\" function?

    \n

    No, an object file cannot be run directly. If you force it to run, it will exec format error.

  • \n
  • Can you \"run\" an executable that contains a single function called \"main()\"?

    \n

    Yes, an executable with just main() can be run.

  • \n
  • Can you \"run\" an executable that does not contain a function called \"main()\"?

    \n

    No, main() is required to run an executable.

  • \n
  • What does the \"-Wall\" flag do?

    \n

    \"-Wall\" enables all compiler warnings

  • \n
  • What does the \"-g\" flag do?

    \n

    \"-g\" adds debugging information.

  • \n
  • What does the \"-ansi\" flag do?

    \n

    \"-ansi\" enables strict ANSI C mode. The \"-ansi\" flag is equivalent to the -\"std=c89\" flag.

  • \n
  • What does the \"-c\" flag do?

    \n

    \"-c\" compiles to object file only, does not link.

  • \n
  • What does the \"-o\" flag do?

    \n

    \"-o\" specifies output file name.

    \n
      \n
    • If \"-c\" is also used with a single [filename].c file, and no other .o in the command line, gcc will default generate an object file named [filename].o. If \"-o\" is used in such a case, it will create an object file with the specified name.
    • \n
    • If no \"-c\" is used, gcc will by default create an executable file named \"a.out\".
    • \n
  • \n
\n

File Operations

\n
    \n
  • Given the following FILE pointer variable definition, write the code that will open a file named \"hello.txt\" for read-only access and print a message of your choice if there was an error in doing so.

    \n

    FILE *my_file = 0;

    \n

    my_file = fopen("hello.txt", "r");
    if (my_file = NULL) {
    fprintf(stdout, "Failed to open the file\\n");
    }

  • \n
  • Write code that will, without opening any file, check if a file named \"hello.txt\" can be opened for read access. Put the code inside the 'if' predicate:

    \n

    if (access("hello.txt", R_OK) == 0) {
    /* Yes, we can open the file... */
    }

  • \n
  • Write code that will, without opening any file, check if a file named \"hello.txt\" can be opened for write access. Put the code inside the 'if' predicate:

    \n

    if (access("hello.txt", W_OK) == 0) {
    /* Yes, we can open the file... */
    }

  • \n
  • Write a function called read_and_print() that will do the following:

    \n
      \n
    • Open a text file called \"hello.txt\" for read-only access.
    • \n
    • Read a word that is terminated by a newline from the file into the character array called \"my_string\".
    • \n
    • Read an integer terminated by a newline into the int variable called \"my_int\".
    • \n
    • Print the string and the integer value.
    • \n
    • Return the my_int value.
    • \n
    • If the file cannot be opened for reading, return -1.
    • \n
    • If an error occurs while reading from the file, return -1.
    • \n
    \n

    int read_and_print() {
    char my_string[100];
    my_int;

    FILE *fp = fopen("hello.txt", "r");
    if(!fp) return -1;

    if (fscanf(fp, "%s", my_string) != 1) {
    fclose(fp);
    fp = NULL;
    return -1;
    }
    if (fscanf(fp, "%d", &my_int) != 1) {
    fclose(fp);
    fp = NULL;
    return -1;
    }
    printf("%s %d\\n", my_string, my_int);
    fclose(fp);
    fp = NULL;
    return my_int;
    }

  • \n
  • Write a function named print_reverse that will open a text file named \"hello.txt\" and print each character in the file in reverse. i.e. print the first character last and the last character first. The function should return the number of characters in the file. Upon any error, return -1. HINT: Use fseek() a lot to do this.

    \n

    int print_reverse(char* filename) {

    FILE* fp = fopen(filename, "r");
    if(!fp) return -1;

    fseek(fp, 0, SEEK_END);
    int size = ftell(fp);

    for (int i = size - 1; i >= 0; i--) {
    fseek(fp, i, SEEK_SET);
    char c = fgetc(fp);
    printf("%c", c);
    }

    fclose(fp);
    fp = NULL;
    return size;
    }

  • \n
  • Write a function that defines a structure, initializes it, writes it to a file called \"struct.out\", closes the file, re-opens the file for read-only access, reads a single structure into a new struct variable, and then closes the file. Print the structure contents to the screen. On any error, return -1. Otherwise, return 0.

    \n

    #include <stdio.h>

    struct Person {
    char name[50];
    int age;
    };

    int write_and_read_struct() {
    struct Person p = { "John Doe", 30 };

    // Write struct to file
    FILE* fp = fopen("struct.out", "w");
    if (!fp) return -1;

    if (fwrite(&p, sizeof(struct Person), 1, fp) != 1) {
    \tfclose(fp);
    \tfp = NULL:
    \treturn -1;
    }
    fclose(fp);

    // Read struct from file
    fp = fopen("struct.out", "r");
    if (!fp) return -1;

    struct Person p2;
    if (fread(&p2, sizeof(struct Person), 1, fp) != 1) {
    fclose(fp);
    fp = NULL;
    return -1;
    }
    fclose(fp);
    fp = NULL;

    // Print struct
    printf("Name: %s, Age: %d\\n", p2.name, p2,age);
    return 0;
    }

  • \n
\n

Typedef

\n
    \n
  • Declare a type called \"my_array_t\" that is an array of 15 floats.

    \n

    typedef float my_array_t[15];

  • \n
  • Declare a type called \"struct_arr_t\" that is an array of 10 structs of the format

    \n

    struct str {
    int x;
    int y;
    };

    \n

    typedef struct str struct_arr_t[10];

  • \n
  • Define a variable called my_str_arr of type struct_arr_type.

    \n

    struct_arr_t my_str_arr;

  • \n
\n

Structures

\n
    \n
  • Can two elements within a structure have the same name?

    \n

    No, two elements cannot have the same name

  • \n
  • Can you initialize a structure like this?

    \n

    struct my_str {
    int x;
    float y;
    } mine = { 0, 0.0 };
    Yes, you can initialize it like that.

  • \n
  • Can you initialize a structure like this?

    \n

    struct my_str {
    int x;
    float y;
    };
    void my_func(int n) {
    my_str mine = { n, 0.0 };
    }
    No, here my_str is not a type. To fix this, use struct str mine = { n, 0.0 }; instead.

  • \n
  • Declare a structure that contains an integer element named i, a floating point element named f, and an array of 20 characters named str (in that order). Name it anything you want.

    \n

    struct mystruct {
    int i;
    float f;
    char str[20];
    };

  • \n
  • Define a variable called \"my_new_struct\" of the type in the previous question.

    \n

    struct mystruct my_new_struct;

  • \n
  • Define a variable called \"my_array_of_structs\" that is an array of 40 structures of the type in the prior two questions.

    \n

    struct mystruct my_array_of_structs[40];

  • \n
  • Define a function called bigger_rectangle() that will accept one argument of the structure type rectangle (declared below) and will multiply the width dimension by 1.5, the height dimension by 2.5 and the length dimension by 3. The function should return the new structure. Define a temporary local variable if you want to.

    \n

    struct rectangle {
    float height;
    float width;
    float length;
    };

    \n

    struct rectangle bigger_rectangle(struct rectangle r) {
    struct rectangle bigger;
    bigger.height = r.height * 2.5;
    bigger.width = r.width * 1.5;
    bigger.length = r.length * 3;
    return bigger;
    }

  • \n
  • Write a function named sum_rectangles that will open a binary file named \"rect.in\" for reading and read the binary images of rectangle structures from it. For each rectangle structure, add its elements to those of the first structure read. e.g. sum the height fields of all the structures, sum the width fields of all the structures, etc... Return a structure from sum_rectangles where each element represents the sum of all structures read from the file. i.e. the height field should be the sum of all of the height fields of each of the structures. On any file error, return the structure { -1.0, -1.0, -1.0 }.

    \n

    #include <stdio.h>

    struct rectangle {
    float height;
    float width;
    float length;
    };

    struct rectangle sum_rectangles() {
    struct rectangle bad_struct = {-1.0, -1.0, -1.0};

    FILE *fp = fopen("rect.in", "rb");
    if(!fp) {
    return bad_struct;
    }

    struct rectangle sum = {0, 0, 0};
    struct rectangle r;

    if (fread(&r, sizeof(struct rectangle), 1, fp) != 1) {
    fclose(fp);
    fp = NULL;
    return bad_struct;
    }

    sum.height = r.height;
    sum.width = r.width;
    sum.length = r.length;

    while (fread(&r, sizeof(struct rectangle), 1, fp) == 1) {
    sum.height += r.height;
    sum.width += r.width;
    sum.length += r.length;
    }

    fclose(fp);
    fp = NULL;
    return sum;
    }

  • \n
\n

assert()

\n
    \n
  • Under what circumstances would you place an assert() into your code?

    \n

    Used to check for logical errors and malformed data.

  • \n
  • What will be the result of the following code:

    \n

    int my_func() {
    int count = 0;
    int sum = 0;

    for (count = 0; count < 100; count++) {
    assert(sum > 0);
    sum = sum + count;
    }
    return sum;
    }
    The program will abort/crash on the assert line.

  • \n
  • What might you do to the previous code to make it do a \"better\" job?

    \n

    Move assert(sum > 0); down, after for loop. Or change to assert(sum >= 0);

  • \n
\n

String Operations

\n
    \n
  • Write a function called do_compare() that will prompt the user for two strings of maximum length 100. It should compare them and print one of the following messages:

    \n
      \n
    • The strings are equal.
    • \n
    • The first string comes before the second.
    • \n
    • The second string comes before the first.
    • \n
    \n

    The function should always return zero.

    \n

    #include <stdio.h>
    #include <string.h>

    int do_compare() {
    char str1[101], str2[101];

    // Prompt the user to enter two strings
    printf("Enter the first string (up to 100 characters): ");
    fgets(str1, sizeof(str1), stdin);

    printf("Enter the second string (up to 100 characters): ");
    fgets(str2, sizeof(str2), stdin);

    // Compare the strings
    int cmp = strcmp(str1, str2);

    // Print the comparison result
    if (cmp == 0) {
    printf("The strings are equal.\\n");
    } else if (cmp < 0) {
    printf("The first string comes before the second.\\n");
    } else {
    printf("The second string comes before the first.\\n");
    }

    return 0;
    }

  • \n
\n

Variables

\n
    \n
  • What is the difference between initialization of a variable and assignment to a variable?

    \n

    Initialization is giving a variable its initial value, typically at the time of declaration, while assignment is giving a new value to an already declared variable at any point after initialization.

  • \n
  • What is the difference between a declaration and a definition?

    \n

    Declaration is announcing the properties of var (no memory allocation), definition is allocating storage for a var and initializing it.

  • \n
  • What is the difference between a global variable and a local variable?

    \n

    Global variables have a broader scope, longer lifetime, and higher visibility compared to local variables, which are limited to the scope of the function in which they are declared.

  • \n
  • For the following questions, assume that the size of an 'int' is 4 bytes, the size of a 'char' is one byte, the size of a 'float' is 4 bytes, and the size of a 'double' is 8 bytes. Write the size of the following expressions:

    \n

    struct my_coord {
    int x;
    int y;
    double altitude;
    };

    struct my_line {
    struct my_coord first;
    struct my_coord second;
    char name[10];
    };

    struct my_coord var;
    struct my_coord array[3];
    struct my_line one_line;
    struct my_line two_lines[2];

    \n

    sizeof(struct my_coord) = __16___

    \n

    sizeof(var) = __16___

    \n

    sizeof(array[1]) = __16___

    \n

    sizeof(array[2]) = __16___

    \n

    sizeof(array) = __48___

    \n

    sizeof(struct my_line) = __48___

    \n

    sizeof(two_lines) = __96___

    \n

    sizeof(one_line) = __48___

    \n

    Explanation: When calculating the size of a struct, we need to consider alignment and padding, which can affect the overall size of the struct. In the case of struct my_line, the total size is influenced by the alignment requirements of its members. The largest member of struct my_coord is double altitude, which is 8 bytes. This means that the double altitude member will determine the alignment and padding for the entire struct my_coord within struct my_line.

    \n

    So here char name[10]; will occupy (10 bytes) + (6 bytes padding to align char[10] on an 8-byte boundary). This ends up with (16+16+10+6) for the size of struct my_line.

    \n

    Remember that the size of the structure should be a multiple of the biggest variable.

  • \n
  • Draw the memory layout of the prior four variables; var, array, one_line, and two_lines on a line of boxes. Label the start of each variable and clearly show how many bytes each element within each structure variable consumes.

  • \n
  • Re-define the two_lines variable above and _initialize_ it's contents with the following values:

    \n

    first my_line structure:
    first my_coord structure:
    x = 1
    y = 3
    altitude = 5.6
    second my_coord structure:
    x = 4
    y = 5
    altitude = 2.1
    name = "My Town"
    second my_line structure:
    first my_coord structure:
    x = 9
    y = 2
    altitude = 1.1
    second my_coord structure:
    x = 3
    y = 3
    altitude = 0.1
    name = "Your Town"

    \n

    struct my_line two_lines[2] = {
    {
    {1, 3, 5.6},
    {4, 5, 2.1},
    "My Town"
    },
    {
    {9, 2, 1.1},
    {3, 3, 0.1},
    "Your Town"
    }
    };

  • \n
  • How many bytes large is the following definition?

    \n
    struct my_coord new_array[] = {
    { 0,0,3.5 },
    { 1,2,4.5},
    { 2,0,9.5}
    };
    \n

    (4 + 4 + 8) * 3 = 48

  • \n
\n

Basic Pointer Operations

\n
    \n
  • What is printed by the following three pieces of code:

    \n

    int x = 0;                int x = 0;                int x = 0;
    int y = 0; int y = 0; int y = 0;
    int *p = NULL; int *p = NULL; int *p = NULL;
    int *q = NULL; int *q = NULL;
    p = &x;
    *p = 5; p = &x; p = &y;
    p = &y; q = p; q = &x;
    *p = 7; *q = 7; p = 2;
    q = 3;
    printf("%d %d\\n", x, y); printf("%d %d\\n", x, y); printf("%d %d\\n", x, y);

    \n

    The 1st column code snippet printed 5 7. The 1st column code snippet printed 7 0. The 1st column code snippet printed 0 0.

  • \n
  • Consider the following variable definitions:

    \n
    int x = 2;
    int arr[10] = {4, 5, 6, 7, 1, 2, 3, 0, 8, 9};
    int *p;
    \n

    And assume that p is initialized to point to one of the integers in arr. Which of the following statements are legitimate? Why or why not?

    \n

    p = arr;      arr = p;      p = &arr[2];   p = arr[x];     p = &arr[x];

    arr[x] = p; arr[p] = x; &arr[x] = p; p = &arr; x = *arr;

    x = arr + x; p = arr + x; arr = p + x; x = &(arr+x); p++;

    x = --p; x = *p++; x = (*p)++; arr++; x = p - arr;

    x = (p>arr); arr[*p]=*p; *p++ = x; p = p + 1; arr = arr + 1;

    \n

    Let's go through each statement to determine if it is legitimate or not, and explain:

    \n
      \n
    • p = arr; - Legitimate. Assigns the address of the first element of arr to p.
    • \n
    • arr = p; - Not legitimate. You cannot assign to an array name.
    • \n
    • p = &arr[2]; - Legitimate. Assigns the address of arr[2] to p.
    • \n
    • p = arr[x]; - Not legitimate. arr[x] is an integer value, not an address.
    • \n
    • p = &arr[x]; - Legitimate. Assigns the address of arr[x] to p.
    • \n
    • arr[x] = p; - Not legitimate. arr[x] is an integer value, not a pointer.
    • \n
    • arr[p] = x; - Not legitimate. arr[p] is not a valid operation. p should be an index, not a pointer.
    • \n
    • &arr[x] = p; - Not legitimate. You cannot assign a value to the address of an element.
    • \n
    • p = &arr; - Not legitimate. &arr is the address of the whole array, not a pointer to an integer.
    • \n
    • x = *arr; - Legitimate. Assigns the value of the first element of arr to x.
    • \n
    • x = arr + x; - Legitimate. Calculates the address of arr[x] and assigns it to x.
    • \n
    • p = arr + x; - Legitimate. Calculates the address of arr[x] and assigns it to p.
    • \n
    • arr = p + x; - Not legitimate. You cannot assign to an array name.
    • \n
    • x = &(arr+x); - Not legitimate. & expects an lvalue, but (arr+x) is not an lvalue.
    • \n
    • p++; - Legitimate. Increments the pointer p to point to the next element.
    • \n
    • x = --p; - Legitimate. Decrements p and assigns its value to x.
    • \n
    • x = *p++; - Legitimate. Assigns the value pointed to by p to x, then increments p.
    • \n
    • x = (*p)++; - Legitimate. Assigns the value pointed to by p to x, then increments the value pointed to by p.
    • \n
    • arr++; - Not legitimate. You cannot increment the entire array arr.
    • \n
    • x = p - arr; - Legitimate. Calculates the difference in addresses between p and arr and assigns it to x.
    • \n
    • x = (p>arr); - Not legitimate. Comparison between a pointer and an array is not valid.
    • \n
    • arr[*p]=*p; - Not legitimate. arr[*p] is not a valid assignment target.
    • \n
    • *p++ = x; - Legitimate. Assigns x to the value pointed to by p, then increments p.
    • \n
    • p = p + 1; - Legitimate. Increments the pointer p to point to the next memory location.
    • \n
    • arr = arr + 1; - Not legitimate. You cannot increment the entire array arr.
    • \n
    \n

    📝Notes: The difference between x = *p++; and x = (*p)++; lies in how the increment operator (++) is applied.

    \n

    \n
      \n
    • x = *p++; This statement first dereferences the pointer p to get the value it points to, assigns that value to x and then increments the pointer p to point to the next element (not the value pointed to by p). So, x gets the value pointed to by p before the increment.
    • \n
    • x = (*p)++; This statement first dereferences the pointer p to get the value it points to, assigns that value to x, and then increments the value pointed to by p. So, x gets the value pointed to by p before the increment, and the value at the memory location pointed to by p is incremented.
    • \n
    \n

    Here's a brief example to illustrate the difference:

    \n

    #include <stdio.h>

    int main() {
    int array[] = {1, 2, 3};
    int *p = array;
    int x;

    // x gets the value pointed to by p, then p is incremented
    x = *p++; // x = 1, p now points to array[1]
    printf("x = %d, array[1] = %d, p points to %d\\n", x, array[1], *p);

    // x gets the value pointed to by p, then the value pointed to
    // by p is incremented
    x = (*p)++; // x = 2, array[1] is now 3
    printf("x = %d, array[1] = %d, p points to %d\\n", x, array[1], *p);
    return 0;
    }

    \n

    The output of the above program is

    \n

    x = 1, array[1] = 2, p points to 2
    x = 2, array[1] = 3, p points to 3

    \n

    To test your understanding, now check the following code snippet, what will the output be:

    \n

    int x = 2, y = 15, z = 0;
    int *p = 0;

    p = &y;
    x = *p++;
    printf("x = %d, y = %d, z = %d\\n", x, y, z);

    p = &y;
    z = (*p)++;
    printf("x = %d, y = %d, z = %d\\n", x, y, z);

    \n

    Answer

    \n
    \n
    x = 15, y = 15, z = 0
    x = 15, y = 16, z = 15
    \n

    So the variable y has its value incremented after z = (*p)++;.

    \n\n

  • \n
  • Given the following definitions:

    \n

    int arr[] = { 0, 1, 2, 3 };
    int *p = arr;
    are the following two statements equivalent?

    \n

    p = p + 1;
    p++;
    What can you say about the result of adding a pointer to an integer?

    \n

    Yes, the two statements p = p + 1; and p++; are equivalent in this context. Both statements increment the pointer p to point to the next element in the array arr.

    \n

    In general, if ptr is a pointer to type T, then ptr + n will point to the memory location \"ptr + n * sizeof(T)\". This is useful for iterating over arrays or accessing elements in memory sequentially.

  • \n
  • Write a function called 'swap' that will accept two pointers to integers and will exchange the contents of those integer locations.

    \n
      \n
    • Show a call to this subroutine to exchange two variables.

      \n

      Here is the sample code:

      \n

      #include <stdio.h>

      void swap(int *a, int *b) {
      int temp = *a;
      *a = *b;
      *b = temp;
      }

      int main() {
      int x = 5, y = 10;

      printf("Before: x = %d, y = %d\\n", x, y);
      swap(&x, &y);
      printf("After: x = %d, y = %d\\n", x, y);
      return 0;
      }

    • \n
    • Why is it necessary to pass pointers to the integers instead of just passing the integers to the Swap subroutine?

      \n

      It is necessary to pass pointers to the integers instead of just passing the integers themselves to the swap subroutine because C passes arguments by value. When you pass an integer to a function, a copy of the integer's value is made and passed to the function. Any changes made to the parameter inside the function do not affect the original variable outside the function.

      \n

      By passing pointers to integers (int *a and int *b), you are passing the memory addresses of the integers. This allows the swap function to access and modify the actual integers in memory, rather than working with copies. As a result, the values of the integers are swapped correctly, and the changes are reflected outside the function.

      \n

      In summary, passing pointers to integers allows the swap function to modify the values of the integers themselves, rather than just copies of the values.

    • \n
    • What would happen if you called swap like this:

      \n

      int x = 5;
      swap(&x, &x);

      \n

      If you called swap(&x, &x); with the same pointer &x for both arguments, it would effectively try to swap the contents of x with itself. The result would be that x would remain unchanged, as the swap operation would effectively cancel itself out. The swap operation had no net effect on x.

    • \n
    • Can you do this: (why or why not?)

      \n

      swap(&123, &456);
      No, you cannot do this because &123 and &456 are not valid addresses in memory. 123 and 456 are constants, not variables, so you cannot take their addresses for swapping the content.

    • \n
  • \n
  • What does the following code print:

    \n
    int func() {
    int array[] = { 4, 2, 9, 3, 8 };
    int *P = NULL;
    int i = 0;

    p = &array[2];
    p++;
    printf("%d\\n", *(p++));
    *(--p) = 7;

    (*p)++;
    for (i = 0; i < (sizeof(array)/sizeof(int)); i++) {
    printf("%d ", array[i]);
    }
    }
    \n

    The output is

    \n
    3
    4 2 9 8 8
    \n

    Explanation:

    \n
      \n
    • Initially, p points to array[2] which is 9.
    • \n
    • After p++, p points to array[3] which is 3. The value 3 is printed.
    • \n
    • Then, *(--p) = 7; sets array[3] to 7.
    • \n
    • Next, (*p)++; increments the value at array[3] (which is now 7) to 8.
    • \n
    • Finally, the for loop prints the elements of the array, which are 4 2 9 8 8.
    • \n
  • \n
  • Write a subroutine called clear_it that accepts a pointer to integer and an integer that indicates the size of the space that the pointer points to. clear_it should set all of the elements that the pointer points to to zero.

    \n

    void clear_it(int *ptr, int size) {
    for (int i = 0; i < size; i++) {
    *(ptr + i) = 0;
    }
    }

  • \n
  • Write a subroutine called add_vectors that accepts three pointers to integer and a fourth parameter to indicate the size of the spaces that the pointers point to. add_vectors should add the elements of the first two 'vectors' together and store them in the third 'vector'. e.g. if two arrays of 10 integers, A and B, were to be added together and the result stored in an array C of the same size, the call would look like add_vectors(a, b, c, 10); and, as a result, c[5] would be the sum of a[5] and b[5]

    \n

    All four implementations below are equivalent solutions to this problem:

    \n

    void add_vectors(int *a, int *b, int *c, int size) {
    for (int i = 0; i < size; i++) {
    c[i] = a[i] + b[i];
    }
    }

    void add_vectors1(int *a, int *b, int *c, int size) {
    int *end = c + size;
    while (c < end) {
    *c++ = *a++ + *b++;
    }
    }

    void add_vectors2(int *a, int *b, int *c, int size) {
    for (int i=0; i<size; i++) {
    *c++ = *a++ + *b++;
    }
    }

    void add_vectors3(int *a, int *b, int *c, int size) {
    for (int i=0; i<size; i++) {
    *(c+i) = *(a+i) + *(b+i);
    }
    }

  • \n
\n","categories":["Study Notes"],"tags":["C/C++ Programming"]},{"title":"Programming in C Exam Review and Practices (II)","url":"/en/2024/03/26/C-Prog-Exam-Review-Practices-2/","content":"

Here is a series of general study guides to college-level C programming courses. This is the second part covering dynamic memory allocation, advanced pointer operations, recursion, linked list and tree common functions, etc.

\n

Dynamic Memory Allocation

\n
    \n
  • Given the following definitions:

    \n

    int *pi = NULL;
    float *pf = NULL;
    char *pc = NULL;
    char my_string[] = "Hello, World!";

    \n

    write statements to do the following memory operations:

    \n
      \n
    • reserve space for 100 integers and assign a pointer to that space to pi

      \n

      pi = (int *)malloc(sizeof(int) * 100);
      assert(pi != NULL);

    • \n
    • reserve space for 5 floats and assign a pointer to that space to pf

      \n

      pf = (float *)malloc(sizeof(float) * 5);
      assert(pf != NULL);

    • \n
    • unreserve the space that pi points to

      \n

      free(pi);
      pi = NULL;

    • \n
    • reserve space for enough characters to hold the string in my_string and assign a pointer to that space to pc. Copy my_string into that space.

      \n

      pc = (char *)malloc(strlen(my_string) + 1));
      assert(pc != NULL);
      strcpy(pc, mystring);

    • \n
    • free everything that hasn't been unreserved yet.

      \n

      free(pc);
      free(pf);
      pc = NULL;
      pf = NULL;

    • \n
  • \n
  • What happens if you reserve memory and assign it to a pointer named p and then reserve more memory and assign the new pointer to p? How can you refer to the first memory reservation?
    \nIf you reserve then assign then reserve more memory you will have a memory leak. If you want to refer to the first pointer, you can set a new pointer to point to the new one before reserving more memory.

  • \n
  • Does it make sense to free() something twice? What's a good way to prevent this from happening?
    \nNo, it doesn’t make sense to free something twice, a good way to prevent this is setting the thing you freed to NULL after freeing it.

  • \n
\n

Advanced Pointer Operations

\n
    \n
  • Suppose p is a pointer to a structure and f is one of its fields. What is a simpler way of saying: x = (*p).f;.

    \n

    x = p->f;

  • \n
  • Given the following declarations and definitions:

    \n

    struct s {
    \tint x;
    \tstruct s *next;
    };
    what will the following code print?

    \n

    struct s *p1 = NULL;
    struct s *p2 = NULL;
    struct s *p3 = NULL;
    struct s *p4 = NULL;
    struct s *p5 = NULL;
    p5 = malloc(sizeof(struct s));
    p5->x = 5;
    p5->next = NULL;
    p4 = malloc(sizeof(struct s));
    p4->x = 4;
    p4->next = p5;
    p3 = malloc(sizeof(struct s));
    p3->x = 3;
    p3->next = p4;
    p2 = malloc(sizeof(struct s));
    p2->x = 2;
    p2->next = p3;
    p1 = malloc(sizeof(struct s));
    p1->x = 1;
    p1->next = p2;
    printf("%d %d\\n", p1->next->next->next->x, p2->next->x);

    \n

    It will print \"4 3\".

  • \n
  • Write a subroutine called do_allocate that is passed a pointer to the head pointer to a list of block structures: do_allocate(struct block **). If the head pointer is NULL, do_allocate should allocate a new struct block and make the head pointer point to it. If the head is not NULL, the new struct block should be prepended to the list, and the head pointer set to point to it.

    \n

    This is a linked list insertion function. New data items should always be inserted into the front of the list. Note the input argument has to be a pointer to pointer to make a change to the original head pointer. A sample solution is shown below

    \n

    #include <stdlib.h>

    struct block {
    int data;
    struct block *next;
    };

    void do_allocate(struct block **head) {
    struct block *new_block = malloc(sizeof(struct block));
    if (new_block == NULL) {
    // Handle memory allocation failure
    return;
    }

    // Initialize the new block
    new_block->data = 0;
    new_block->next = *head;

    // Update the head pointer
    *head = new_block;
    }

  • \n
  • Write a subroutine called my_free that will accept a pointer to a pointer of some arbitrary type and:

    \n
      \n
    • free the space pointed to by the pointer
    • \n
    • set the pointer to NULL
    • \n
    \n

    #include <stdlib.h>

    void my_free(void **ptr) {
    if (ptr != NULL && *ptr != NULL) {
    free(*ptr);
    *ptr = NULL;
    }
    }

  • \n
  • Given the following declaration:

    \n

    struct employee {
    char *name;
    char *title;
    int id;
    };
    write a subroutine called create_employee that accepts two string parameters for the new name and title and one integer parameter for the ID. It should return a newly allocated Employee structure with all of the fields filled in.

    \n

    #include <stdlib.h>
    #include <string.h>

    struct employee *create_employee(const char *name, const char *title, int id)
    {
    struct employee *new_employee = malloc(sizeof(struct employee));
    if (new_employee == NULL) {
    return NULL;
    }

    // Allocate memory for the name and copy the string
    new_employee->name = malloc(strlen(name) + 1);
    if (new_employee->name == NULL) {
    free(new_employee);
    return NULL;
    }
    strcpy(new_employee->name, name);

    // Allocate memory for the title and copy the string
    new_employee->title = malloc(strlen(title) + 1);
    if (new_employee->title == NULL) {
    free(new_employee->name);
    free(new_employee);
    return NULL;
    }
    strcpy(new_employee->title, title);

    // Set the ID
    new_employee->id = id;

    return new_employee;
    }

  • \n
  • Write a subroutine called fire_employee that accepts a pointer to pointer to struct employee, frees its storage and sets the pointer that points to the storage to NULL.

    \n

    void fire_employee(struct employee **emp_ptr) {
    if (emp_ptr != NULL && *emp_ptr != NULL) {
    \tfree((*emp_ptr)->name);
    \tfree((*emp_ptr)->title);
    \tfree(*emp_ptr);
    \t*emp_ptr = NULL;
    }
    }

  • \n
\n

Recursion

\n
    \n
  • Create a recursive function to compute the factorial function.

    \n

    unsigned long long factorial(unsigned int n) {
    if (n == 0) return 1;
    return n * factorial(n - 1);
    }

  • \n
  • Create a recursive function to compute the Nth element of the Fibonacci sequence: 0 1 1 2 3 5 8 13 21 34 55 ...

    \n

    unsigned int fibonacci(unsigned int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
    }

  • \n
  • Implement a recursive list search. e.g. each function call should either return the list node that it's looking at because it matches the search item or it should return the value from calling itself on the next item in the list.

    \n

    struct Node {
    int data;
    struct Node* next;
    };

    struct Node* search(struct Node* node, int value) {
    if (node == NULL) return NULL;

    if (node->data == value) {
    return node;
    } else {
    // Recursive call on the next node
    return search(node->next, value);
    }
    }

  • \n
\n

Linked List Functions

\n
#include <stdio.h>
#include <stdlib.h>

struct Node {
int data;
struct Node* next;
};


// Assume the list is ordered with decreasing date values,
// insert before all nodes with less or equal data values.
// [7, 5, 5, (new:4), 4, 2, 1]

void insertBefore(struct Node** head, struct Node* newNode) {
if (*head == NULL) {
// If the head is NULL, insert the new node as the first node
newNode->next = NULL;
*head = newNode;
return;
}

// The first node's value is less than or equal to the new node's,
// insert the new node as the new first node.
if ((*head)->data <= newNode->data) {
newNode->next = *head;
*head = newNode;
return;
}

struct Node* current = *head;
while (current->next != NULL && current->next->data > newNode->data) {
current = current->next;
}

newNode->next = current->next;
current->next = newNode;
}

// Assume the list is ordered with decreasing date values,
// insert after all nodes with greater or equal data values.
// [7, 5, 5, 4, (new:4), 2, 1]

void insertAfter(struct Node** head, struct Node* newNode) {
if (*head == NULL) {
// If the head is NULL, insert the new node as the first node
newNode->next = NULL;
*head = newNode;
return;
}

// The first node's value is less than the new node's,
// insert the new node as the new first node.
if ((*head)->data < newNode->data) {
newNode->next = *head;
*head = newNode;
return;
}

struct Node* current = *head;
while (current->next != NULL && current->next->data >= value) {
current = current->next;
}

newNode->next = current->next;
current->next = newNode;
}

void insertAtBeginning(struct Node** head, struct Node* newNode) {
newNode->next = *head;
*head = newNode;
}

void insertAtTail(struct Node** head, struct Node* newNode) {
if (*head == NULL) {
*head = newNode;
return;
}

struct Node* current = *head;
while (current->next != NULL) {
current = current->next;
}

current->next = newNode;
newNode->next = NULL;
}

void printList(struct Node* node) {
while (node != NULL) {
printf("%d ", node->data);
node = node->next;
}
printf("\\n");
}

int main() {
struct Node* head = NULL;
struct Node* node1 = (struct Node*)malloc(sizeof(struct Node));
node1->data = 1;
node1->next = NULL;

struct Node* node2 = (struct Node*)malloc(sizeof(struct Node));
node2->data = 3;
node2->next = NULL;

struct Node* node3 = (struct Node*)malloc(sizeof(struct Node));
node3->data = 5;
node3->next = NULL;

insertAtBeginning(&head, node1);
insertAfter(&head, node2);
insertBefore(&head, node3, 4);
insertAtTail(&head, node3);

printf("Linked list after insertion: ");
printList(head);

return 0;
}
\n

Tree Common Functions

\n
#include <stdio.h>
#include <stdlib.h>

struct Node {
int data;
struct Node* left;
struct Node* right;
};

struct Node* createNode(int value) {
struct Node* newNode = malloc(sizeof(struct Node));
newNode->data = value;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

struct Node* insertNode(struct Node* root, int value) {
if (root == NULL) {
return createNode(value);
}

if (value < root->data) {
root->left = insertNode(root->left, value);
} else if (value > root->data) {
root->right = insertNode(root->right, value);
}

return root;
}

struct Node* minValueNode(struct Node* node) {
struct Node* current = node;
while (current && current->left != NULL) {
current = current->left;
}
return current;
}

struct Node* maxValueNode(struct Node* node) {
struct Node* current = node;
while (current && current->right != NULL) {
current = current->right;
}
return current;
}

void inorderTraversal(struct Node* root) {
if (root != NULL) {
inorderTraversal(root->left);
printf("%d ", root->data);
inorderTraversal(root->right);
}
}

int main() {
struct Node* root = NULL;
root = insertNode(root, 50);
insertNode(root, 30);
insertNode(root, 20);
insertNode(root, 40);
insertNode(root, 70);
insertNode(root, 60);

printf("Inorder traversal: ");
inorderTraversal(root);
printf("\\n");

return 0;
}
\n

Local, Static and Global Variables

\n
    \n
  • Try the following two programs to appreciate the differences between static and non-static local variables.

    \n

    void try() {                    void try() {
    int x = 0; static int x = 0;
    if (x == 0) { if (x == 0) {
    x = 5; x = 5;
    } }
    x++; x++;
    printf("X = %d\\n", x); printf("X = %d\\n", x);
    } }
    int main() { int main() {
    int i=0; int i=0;
    for (i=0; i<10; i++) for (i=0; i<10; i++)
    try(); try();
    } }
    // Output "X = 6" always // Output "X = 6/7/8/..."

  • \n
  • What happens if you define a global variable with a static storage class in one module and attempt to refer to that variable in a different module?
    \nThe variable will not be accessible in the other module. This is because static variables have internal linkage by default, meaning they are only accessible within the same module.

  • \n
  • Can a function be declared with a static storage class? If so, how? If not, why not?
    \nYes, you can declare a function with the static storage class, you can use the static keyword. It means that the function has internal linkage, which restricts its scope to the current translation unit (i.e., the source file in which it is defined). This means that the function can only be called from within the same source file, and its name is not visible outside of that file.

  • \n
  • Create a global variable in one module and, in another module use an \"extern\" declaration to refer to it.

    \n

    module1.c
    int globalVariable = 42;

    \n

    module2.c
    extern int globalVariable; // Declare the global variable from module1

    int main() {
    printf("The value of globalVariable is: %d\\n", globalVariable);
    return 0;
    }

  • \n
\n

Types

\n
    \n
  • Under what conditions can you qualify a type as \"const\"?
    \nThe const keyword is used to indicate that the value of the object with that type cannot be modified.

  • \n
  • What is the difference between the following types?

    \n

    const char * cp1;
    char * const cp2;
    const char * const cp3;

    \n

    const char * cp1;: This declares cp1 as a pointer to a constant char. It means that the data cp1 points to cannot be modified through cp1, but cp1 itself can be changed to point to a different memory location.

    \n

    char * const cp2;: This declares cp2 as a constant pointer to a char. It means that cp2 always points to the same memory location, and this memory location cannot be changed. However, the data at this memory location can be modified through cp2.

    \n

    const char * const cp3;: This declares cp3 as a constant pointer to a constant char. It means that both cp3 and the data it points to are constant. cp3 cannot be changed to point to a different memory location, and the data it points to cannot be modified through cp3.

    \n

    In summary:

    \n
      \n
    • const to the left of * makes the data constant.
    • \n
    • const to the right of * makes the pointer constant.
    • \n
    • const on both sides makes both the pointer and the data constant.
    • \n
  • \n
  • Name all of the first-class types in \"C\".
    \nScalar types (e.g., int, float, double, char, void, short, long, etc.)

  • \n
  • Give an example of a derived type in \"C\".
    \nPointer types (e.g., int *, char *, etc.).
    \nPointer to function types (e.g., int (*)(int, int), a pointer to a function that takes two int arguments and returns an int)

    \n

    An example is declaring a struct type, e.g.:

    \n

    struct person {
    \tchar name[20];
    \tint age;
    \tfloat height;
    };

  • \n
  • Can you assign a float variable to an int variable?
    \nYes, but the value will be truncated.

  • \n
  • Can you assign an int variable to a float variable?
    \nYes, but the type will be promoted.

  • \n
  • Can you assign any first-class type variable to any other first-class type variable?
    \nYes, you just have to typecast them to the matching data type.

  • \n
  • Can you assign a first-class type variable to any kind of derived type variable?
    \nNo, e.g. you cannot assign an int to a structure

  • \n
\n

C Preprocessor and Libraries

\n
    \n
  • Review how to use the following preprocessor directives:
  • \n
\n
#define SOMETHING SOMETHING_ELSE
...
#ifdef SOMETHING
...
#else
...
#endif
\n

#define is a preprocessor directive in C that unconditionally defines a macro.

\n

#ifdef is a preprocessor directive in the C programming language that tests whether a macro has been defined or not. It allows conditional compilation of code based on whether a particular macro has been defined or not.

\n

#else is run if the macro is not defined in a #ifdef

\n

#endif Ends a #ifdef macro

\n
#if (SOMETHING == 5)
\n

#if is a preprocessor directive in the C programming language that allows conditional compilation of code based on the value of an expression.

\n
    \n
  • Does the following program cause a compile-time error?

    \n

    #define C 1
    #define A B
    #define B C
    int function() {
    \tint x = 0;
    #if (A == 1)
    \treturn;
    #else
    \treturn 0;
    #endif
    }
    No, no compile time error. The macro A is defined as B and B is defined as C. So when the preprocessor replaces A in the #if directive, it replaces it with B and then replaces B with C. Therefore, the #if statement is effectively replaced by #if (C == 1).

    \n

    Since C is defined as 1, the condition in the #if statement evaluates to true, and the code in the first branch of the if statement is executed, which is a return statement without a value.

    \n

    In this specific case, the program still works because the function return type is int, and the return statement in the first branch of the if statement might just return some undetermined number.

    \n

    In general, however, it is good practice to always explicitly return a value from a function that has a return type, as it makes the code more clear and less error-prone.

  • \n
  • What are the reasons for using libraries?
    \nTo import useful code, promote modular programming, and provide cross-platform compatibility.

  • \n
  • What are the differences between static and dynamic (shared) libraries?

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    AspectsStatic libraryDynamic library
    LinkingLinked at compile timeLinked at run time
    SizeIncrease the size of the executable (the library code is included in the executable.Reduce the size of the executable (the library code is stored separately and referenced at run time)
    Memory UsageIncrease memory usage (the entire library code is loaded into memory)Reduce memory usage (the code is shared among multiple processes, and only one copy of the library code is loaded into memory)
    Ease of UpdatesRequire recompilation of the entire programAllow for easier updates (can replace the library file without recompiling the program)
    PortabilityMore portable (does not require the presence of the library file at run time)Less portable (requires the library file to be present and correctly configured at run time)
    Runtime DependenciesNo (directly included in the executable)Yes (must be present in the correct location for the program to run)
  • \n
  • What are the trade-offs between the above two?
    \nThe trade-offs between static and dynamic libraries involve executable size, memory usage, ease of updates, runtime dependencies, portability, and performance considerations.

  • \n
  • How do you create a library?
    \nCompile c files into an object file and link them with

    \n

    gcc (name).o –shared –o library.so

  • \n
\n","categories":["Study Notes"],"tags":["C/C++ Programming"]},{"title":"Does Diffie-Hellman Key Exchange Use a Technology Similar to RSA?","url":"/en/2022/11/21/DH-and-RSA/","content":"

Recently, at a WPA3 technology introduction meeting within the R&D team, the speaker mentioned that the OWE technology for encrypted wireless open networks is based on Diffie-Hellman key exchange, and casually said that Diffie-Hellman key exchange is using technology similar to RSA. This statement is wrong! Although Diffie-Hellman key exchange and RSA encryption algorithms belong to public key cryptography, their working mechanisms and application scenarios are different. As a research and development engineer and technician supporting network security, it is necessary to clearly understand the working mechanism and mathematical principles of the two, as well as the differences and connections between them.

\n

A cryptographic system should be secure even if everything about the system, except the key, is public knowledge.
Auguste Kerckhoffs (Dutch linguist and cryptographer, best known for his “Kerckhoffs's principle” of cryptography)

\n
\n

Diffie-Hellman Key Exchange

\n

Diffie-Hellman key exchange (DH for short) is a secure communication protocol that allows two communicating parties to exchange messages over an insecure public channel to create a shared secret without any foreknowledge. This secret can be used to generate keys for subsequent communications between the two parties using symmetric encryption techniques (e.g. AES).

\n

The idea of ​​this kind of public key distribution to achieve shared secrets was first proposed by Ralph Merkle, a doctoral student of Stanford University professor Martin Hellman, and then Professor Hellman's research assistant Whitfield Diffie and Professor Herman jointly invented a practical key exchange protocol. In 1976, Diffie and Hellman were invited to publish their paper \"New Directions in Cryptography\" in IEEE Transactions on Information Theory, which laid the foundation for the public key cryptography system and officially announced the birth of the new Diffie-Herman key exchange technology.

\n

The working principle of Diffie-Hellman key exchange is based on the modular exponentiation operation with the multiplicative group of integers modulo n and its primitive root modulo n in number theory. The following is a simple and specific example to describe:

\n
    \n
  1. Alice chooses a prime number \\(p=71\\), and then a primitive root \\(g=7\\) of the multiplicative group of integers modulo \\(p\\)
  2. \n
  3. Alice chooses a random number \\(a=17\\) that is less than \\(p\\), calculate \\(A=g^a\\bmod\\;p=7^{17}\\bmod\\;71 = 62\\)
  4. \n
  5. Alice sends all \\((p,g,A)\\) to Bob
  6. \n
  7. Bob also chooses a random number \\(b=39\\) that is less than \\(p\\), calculate \\(B=g^b\\bmod\\;p=7^{39}\\bmod\\;71 = 13\\)
  8. \n
  9. Bob sends \\(B\\) back to Alice
  10. \n
  11. Alice calculates \\(s=B^a\\bmod\\;p=13^{17}\\bmod\\;71 = 42\\)
  12. \n
  13. Bob calculate \\(s=A^b\\bmod\\;p=62^{39}\\bmod\\;71 = 42\\)
  14. \n
\n

Is it troublesome calculating \\(\\color{#93F}{\\bf62^{39}\\bmod\\;71}\\)? It is actually very easy……

\n
\n

Remember that modular arithmetic has the property of preserving primitive operations: \\[(a⋅b)\\bmod\\;m = [(a\\bmod\\;m)⋅(b\\bmod\\;m)]\\bmod\\;m\\] Combining with the principle of Exponentiation by Squaring, and applying the right-to-left binary method to do fast calculation: \\[\\begin{align}\n62^{39}\\bmod\\;71 & = (62^{2^0}⋅62^{2^1}⋅62^{2^2}⋅62^{2^5})\\bmod\\;71\\\\\n& = (62⋅10⋅(62^{2^1}⋅62^{2^1})⋅(62^{2^4}⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅(10⋅10)⋅(62^{2^3}⋅62^{2^3}⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅(29⋅29⋅62^{2^3}⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅(60⋅60⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅(50⋅50))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅15)\\bmod\\;71\\\\\n& = 42\n\\end{align}\\]

\n\n
\n

As if by magic, both Alice and Bob get the same \\(s\\) value of \\(42\\). This is the shared secret of two people! After this, Alice and Bob can use the hash value of \\(s\\) as a symmetric key for encrypted communication, which is unknown to any third party.

\n

Why? Because of the nature of the modular exponentiation of the multiplicative group, \\(g^{ab}\\) and \\(g^{ba}\\) are equal with the modulo \\(p\\):

\n

\\[A^b\\bmod\\;p=g^{ab}\\bmod\\;p=g^{ba}\\bmod\\;p=B^a\\bmod\\;p\\]

\n

So calculated \\(s\\) values ​​must be the same. Of course, real applications would use much larger \\(p\\), otherwise the attacker can exhaust all the remainder to try to crack the ciphertext encrypted by the symmetric key.

\n

Notice \\((p,g,A,B)\\) is public and \\((a,b,s)\\) is secret. Now suppose an eavesdropper Eve can see all the messages between Alice and Bob, can she deduce \\(s\\)? The answer is that this is only practically possible if the values of \\((p,a,b)\\) are very small. Eve must first invert \\((a,b)\\) from what she knows about \\((p,g,A,B)\\):

\n
    \n
  • \\(A=g^a\\bmod\\;p\\Rightarrow \\color{fuchsia}{a = log_g A\\bmod\\;p}\\)
  • \n
  • \\(B=g^b\\bmod\\;p\\Rightarrow \\color{fuchsia}{b = log_g B\\bmod\\;p}\\)
  • \n
\n

This is the famous discrete logarithm problem. It is a recognized computational challenge and no polynomial-time efficient algorithm is currently found to compute the discrete logarithm. So this protocol is considered eavesdropping-safe as long as the appropriate \\((p,a,b)\\) is chosen. RFC 3526 recommends 6 Modular Exponential (MODP) DH groups of large prime numbers for practical applications, the smallest of which has 1536 bits!

\n

It should also be emphasized that Diffie-Hellman key exchange itself does not require authentication of both communicating parties, so it is vulnerable to man-in-the-middle attacks. If an attacker can tamper with the messages sent and received by both sides in the middle of the channel, he can complete Diffie-Hellman key exchange twice by pretending to be an identity. The attacker can then decrypt the entire message. Therefore, usually practical applications need to incorporate authentication mechanisms to prevent such attacks.

\n

Diffie-Hellman key exchange technique is a crucial contribution to modern cryptography. In 2015, 39 years after the announcement of this invention, Diffie and Hellman jointly won the ACM Turing Award, known as the \"Nobel Prize of Computing\". The ACM award poster directly stated that they \"invented public key cryptography\".

\n

\n

RSA Encryption Algorithm

\n

RSA is a public key encryption algorithm. The public key encryption system with the same name as the core technology is widely used in secure data transmission. Today, the comprehensive development of the Internet has provided great convenience to the public in all aspects of society. Whether you are surfing, gaming, entertaining, shopping, instant messaging with friends and family, managing a bank account, investing in financial securities, or simply sending and receiving email, RSA is working behind the scenes to protect your privacy and data security.

\n

RSA is actually an acronym for the last names of three people: American cryptographer Ronald Rivest, Israeli cryptographer Adi Shamir, and American computer scientist Leonard Max Adleman. In 1977, Levister, Shamir, and Adleman collaborated at the Massachusetts Institute of Technology (MIT) to invent the RSA encryption algorithm. The algorithm was first published in a public technical report at MIT, and later compiled and published in the February 1978 issue of ACM Communications under the title \"A Method for Obtaining Digital Signatures and Public Key Cryptosystems\".

\n

The basic idea of RSA is that the user creates a key pair consisting of a public key and a private key. The public key is freely distributed and the private key must be kept secret. Anyone can encrypt a message with the public key, and the resulting ciphertext can only be deciphered by the private key holder. On the other hand, any message encrypted with the private key can be decrypted by the public key. Since we assume that the private key can only be held by a specific object, encrypting with the private key is equivalent to generating a digital signature, and decrypting with the public key is equivalent to verifying the signature.

\n

The RSA encryption algorithm consists of a four-step operational process: key generation, key distribution, encryption, and decryption. A simple and concrete example is also given below to illustrate.

\n
    \n
  1. Alice randomly chooses two prime numbers \\(p=127\\) and \\(q=5867\\), computes \\(N=pq=745109\\)
  2. \n
  3. Alice computes Carmichael's totient function \\(\\lambda(N)=\\lambda(745109)=52794\\)\n
      \n
    • When \\(p\\) and \\(q\\) are both primes, \\(\\lambda(pq)=\\mathrm{lcm}(p − 1, q − 1)\\)
    • \n
    • \\(\\mathrm{lcm}\\) represents the function for the least common multiple, which may be calculated through the Euclidean algorithm
    • \n
    • \\(\\mathrm{lcm}(126,5866)=52794\\)
    • \n
  4. \n
  5. Alice chooses an integer \\(e=5\\) less than \\(\\lambda(N)\\) but also coprime with \\(\\lambda(N)\\), and calculates the modular multiplicative inverse of \\(e\\) modulo \\(\\lambda(N)\\). That is \\(d\\equiv e^{-1}\\pmod {\\lambda(N)}\\), \\(d=10559\\)\n
      \n
    • The definition of modular multiplicative inverse is, determine \\(d\\) such that \\((d⋅e)\\;\\bmod\\;\\lambda(N)=1\\)
    • \n
    • \\(d=10559\\equiv 5^{-1}\\pmod {52794}\\)
    • \n
  6. \n
  7. \\(\\pmb{(N,e)}\\) is Alice's public key\\(\\pmb{(N,d)}\\) is her private key\n
      \n
    • Alice sends her public key \\((745109,5)\\) to Bob
    • \n
    • Alice saves her private key \\((745109,10559)\\) in a secret place
    • \n
    • Alice distroies all records of \\(p,q,\\lambda(N)\\)
    • \n
  8. \n
  9. When Bob wants to send Alice a message \\(M\\), according to the encoding format agreed upon by both parties, he first translates \\(M\\) to one or more positive integers \\(m\\) that are all less than \\(N\\), and then uses Alice's public key to compute the ciphertext \\(c\\) one by one. The calculation formula is \\(\\pmb{c\\equiv m^e\\pmod N}\\)\n
      \n
    • Assume \\(M\\) is \"CACC 9678\", and the encoding scheme is 0 for spaces, 1-26 for a-z/A-Z (ignoring case), and 27-36 for 0-9
    • \n
    • Encoding yields the positive integer string \"030103 030036 333435\". Note that each integer is less than 745109
    • \n
    • After encryption, it becomes ciphertext integer string \"184539 741303 358095\"\n
        \n
      • \\(184539 \\equiv 30103^5\\pmod {745109}\\)
      • \n
      • \\(741303 \\equiv 30036^5\\pmod {745109}\\)
      • \n
      • \\(358095 \\equiv 333435^5\\pmod {745109}\\)
      • \n
    • \n
  10. \n
  11. After Alice receives the ciphertext integer string, she uses her private key to compute the plaintext one by one, the calculation formula is \\(\\pmb{m\\equiv c^d\\pmod N}\\)\n
      \n
    • \\(30103 \\equiv 184539^{10559}\\pmod {745109}\\)
    • \n
    • \\(30036 \\equiv 741303^{10559}\\pmod {745109}\\)
    • \n
    • \\(333435 \\equiv 358095^{10559}\\pmod {745109}\\)
    • \n
  12. \n
\n

The third step above works out \\(d\\) from \\(\\color{#93F}{\\bf(d\\cdot 5)\\;mod\\;52794=1}\\), here's how

\n
\n

The modular multiplicative invers can be solved quickly by applying the Extended Euclidean algorithm. Referring to this Wiki page, with the precondition of coprime, the following equation can be written (\\(gcd\\) is the function for the greatest common divisor function):

\n

\\[52794s+5t=\\mathrm{gcd}(5, 52794)=1\\]

\n

The goal is to find the smallest positive integer \\(t\\) that satisfies the above equation. The following table shows the iterative process of the algorithm:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Index \\(i\\)Quotient \\(q_{i-1}\\)Remainder \\(r_i\\)\\(s_i\\)\\(t_i\\)
0\\(52794\\)\\(1\\)\\(0\\)
1\\(5\\)\\(0\\)\\(1\\)
2\\(52794 \\div5 = 10558\\)\\(4\\)\\(1 - 10558\\times 0 = 1\\)\\(0 - 10558\\times 1 = -10558\\)
3\\(5 \\div4 = 1\\)\\(1\\)\\(0-1\\times1 = -1\\)\\(1 - 1\\times (-10558) = \\bf10559\\)
\n

It only takes two iterations to get the remainder \\(1\\) and the algorithm ends. The final \\(t\\) is the \\(5^{-1}\\pmod {52794}\\) we want.

\n\n
\n

String together after decoding to get the same information \"CACC 9678\". Why does Alice's decrypted message match exactly the one sent by Bob? The reason lies in the modular exponentiation operation. First of all, because \\(c\\equiv m^e\\pmod N\\), we can get \\(c^d\\equiv (m^e)^d \\equiv m^{ed} \\pmod N\\). Since \\((d⋅e)\\;mod\\;\\lambda(N)=1\\), it is deduced that \\(ed = 1 + h\\lambda(N)\\) (\\(h\\) is a non-negative integer为非负整数). Combine these two

\n

\\[\\Rightarrow m^{ed} = m^{(1+h\\lambda(N))} = \\color{fuchsia}{m(m^{\\lambda(N)})^h \\equiv m(1)^h}\\equiv m\\pmod N\\]

\n

The penultimate congruence above (symbol \\(\\equiv\\)) is based on Euler's theorem). This proves the correctness of the decryption formula \\({m\\equiv c^d\\pmod N}\\)! You can also see that the order of \\(e\\) and \\(d\\) is irrelevant for the result of \\(m^{ed}\\pmod N\\), so the message that Alice encrypted with her private key can be decrypted by Bob with Alice's public key. This also proves the feasibility of digital signatures.

\n

In terms of security, if a third party can derive \\(d\\) from Alice's public key \\((N,e)\\), then the algorithm is broken. But the prerequisite for cracking is to first identify \\(p\\) and \\(q\\) from \\(N\\), which is very difficult when \\(N\\) is big. In fact, this is the famous problem of factoring large numbers, another recognized computational challenge. So far, \"the best-known algorithms are faster than exponential order of magnitude times and slower than polynomial order of magnitude times.\" The latest record, published on the RSA Factoring Challenge website, is the February 2020 crack of RSA-250, a large number of 829 bits. This development indicates that the security of 1024-bit \\(N\\)-valued public keys is already in jeopardy. In view of this, National Institute of Standards and Technology (NIST) recommends that RSA keys be at least 2048 bits in length for real-world applications.

\n

On the other hand, although the public key does not need to be transmitted confidentially, it is required to be reliably distributed. Otherwise, Eve could pretend to be Alice and send her own public key to Bob. If Bob believes it, Eve can intercept all messages passed from Bob to Alice and decrypt them with her own private key. Eve will then encrypt this message with Alice's public key and pass it to her. Alice and Bob cannot detect such a man-in-the-middle attack. The solution to this problem is to establish a trusted third-party authority to issue certificates to ensure the reliability of public keys. This is the origin of the Public Key Infrastructure (PKI).

\n

The RSA public key encryption algorithm is the genius creation of three cryptographers and computer scientists. Its invention is a new milestone in public key cryptography and has become the cornerstone of modern secure Internet communication. The outstanding contribution of Levister, Shamir, and Adelman earned them the ACM Turing Award in 2002, a full 13 years before Diffie and Herman!

\n

\n

Difference and Connection

\n

The following table summarizes the comparison of Diffie-Hellman key exchange and RSA public key encryption algorithm:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Cryptographic TechnologyDiffie-Hellman Key ExchangeRSA Encryption Algorithm
Technology CategoryAsymmetric, Public Key TechnologyAsymmetric, Public Key Technology
Mathematical PrinciplesInteger modulo \\(n\\) multiplicative groups, primitive rootsCarmichael function, modular multiplicative inverse, Euler's theorem
Mathematical OperationsModular exponentiation, exponentiation by squaringModular exponentiation, exponentiation by squaring, extended Euclidean algorithms
Public Key\\((p,g,A,B)\\)\\((N,e)\\)
Private Key\\((a,b,s)\\)\\((N,d)\\)
SecurityDiscrete logarithm problemLarge number prime factorization problem
Typical ApplicationsKey ExchangeEncryption/Decryption, Digital Signature
Key Kength\\(\\ge2048\\) bits\\(\\ge2048\\) bits
AuthenticationRequires external supportRequires PKI support for public key distribution
Forward SecrecySupportNot support
\n

As can be seen, both are asymmetric public key techniques, and both have a public and private key pair. They both use Modular exponentiation and exponentiation by squaring mathematical operations, and the RSA public-key encryption algorithm also requires the application of the extended Euclidean algorithm to solve the modular multiplicative inverse. Despite these similarities, the mathematical principles underlying them are different, and the computational challenges corresponding to their security are different in nature. These characteristics determine that the Diffie-Hellman key exchange can be used for key exchange, but not for encryption/decryption, while the RSA public key encryption algorithm can not only encrypt/decrypt but also support digital signatures. Therefore, the argument that the two use similar technologies cannot be established in general.

\n

ElGamal encryption based on the evolution of the Diffie-Hellman key exchange can be used to encrypt/decrypt messages, but due to some historical reasons and the great commercial success of the RSA public key encryption algorithm, ElGamal encryption is not popular.

\n
\n

In modern cryptography, key length is defined as the number of bits of a key used by an encryption algorithm. Theoretically, since all algorithms may be cracked by brute force, the key length determines an upper limit on the security of an encryption algorithm. Cryptanalytic study shows that the key strengths of Diffie-Hellman key exchange and RSA public key encryption algorithm are about the same. The computational intensities for breaking discrete logarithms and factoring large numbers are comparable. Therefore, the recommended key length for both cryptographic technologies in practical applications is at least 2048 bits.

\n

For authentication, Diffie-Hellman key exchange requires external support, otherwise it is not resistant to man-in-the-middle attacks. RSA public key encryption algorithm can be used to verify digital signatures, but only if there is a PKI supporting reliable public key distribution. The current system of PKI is quite mature, and there is a special Certificate Authority (CA) that undertakes the responsibility of public key legitimacy checking in the public key system, as well as issues and manages public key digital certificates in X.509 format.

\n

One problem with the RSA public key encryption algorithm in practice is that it does not have Forward Secrecy. Forward Secrecy, sometimes referred to as Perfect Forward Secrecy, is a security property of confidential communication protocols, meaning that the leakage of the long-term used master key does not result in the leakage of past session information. If the system has forward secrecy, it can protect the historical communication records in case of private key leakage. Imagine a situation where, although Eve cannot decrypt the RSA-encrypted messages between Alice and Bob, Eve can archive the entire past message ciphertext. One day in the future, Alice's private key for some reason was leaked, then Eve can decrypt all the message records.

\n

The solution to this problem is Diffie-Hellman key exchange! Remember that the \\((A,B)\\) in the public key of the Diffie-Hellman key exchange is generated by both parties from their respective private keys \\((a,b)\\), so if a random \\((a,b)\\) value is generated at each session, future key leaks will not crack the previous session key. This shows that Diffie-Hellman key exchange supports forward secrecy! If we combine the forward secrecy of Diffie-Hellman key exchange with the digital signature feature of the RSA public key encryption algorithm, we can implement a key exchange with authentication protection. This process can be simplified by the following example.

\n
    \n
  1. Alice and Bob exchange authenticated RSA public key certificates
  2. \n
  3. Alice and Bob each generate a random \\((a,b)\\) value and compute \\((A,B)\\) using the shared Diffie-Hellman \\((p,g)\\).
  4. \n
  5. Alice encrypts \\(A\\) with her RSA private key to generate a digital signature, which she sends to Bob along with \\(A\\)
  6. \n
  7. Bob encrypts \\(B\\) with his own RSA private key to generate a digital signature and sends it to Alice along with \\(B\\).
  8. \n
  9. Alice verifies the signature with Bob's RSA public key, confirms that \\(B\\) came from Bob, and computes \\(s\\) using \\((p,a,B)\\). 6.
  10. \n
  11. Bob verifies the signature with Alice's RSA public key, confirms that \\(A\\) came from Alice, and computes \\(s\\) using \\((p,b,A)\\)
  12. \n
  13. Alice and Bob agree to share a secret and generate a subsequent symmetric encryption (AES) session key for confidential communication
  14. \n
\n

Here the RSA digital signature safeguards the key exchange from man-in-the-middle attacks. Also in the second step above, if a new random number is generated for each session, then even if Alice's or Bob's RSA private keys are leaked one day, it does not threaten the security of previous sessions because the eavesdropper still has to solve the discrete logarithm puzzle. We have also achieved forward secrecy. In fact, this is the working mechanism of the DHE-RSA cipher suite as defined by the ubiquitous Transport Layer Security (TLS) protocol.

\n

DHE-RSA Cipher Suite

\n

Transport Layer Security (TLS) and its predecessor Secure Sockets Layer (SSL) is a security protocol that provides security and data integrity for Internet communications. TLS is widely used in applications such as browsers, email, instant messaging, VoIP, and virtual private networks (VPNs), and has become the de facto industry standard for secure Internet communications. Currently, TLS 1.2 is the commonly supported version of the protocol, supporting secure connections over TCP. Datagram Transport Layer Security (DTLS) protocol is also defined for UDP applications. DTLS is much the same as TLS, with some extensions for connectionless UDP transport in terms of reliability and security. DTLS 1.2 matches the functionality of TLS 1.2.

\n

The TLS protocol uses a client-server architectural model. It works by using X.509 authentication and asymmetric encryption algorithms to authenticate the communicating parties, after which keys are exchanged to generate a symmetric encryption session key. This session key is then used to encrypt the data exchanged between the two communicating parties, ensuring the confidentiality and reliability of the information without fear of attack or eavesdropping by third parties. For identification purposes, the TLS 1.2 protocol combines the authentication, key exchange, bulk encryption, and message authentication code algorithms used into the Cipher Suite name. Each Cipher Suite is given a double-byte encoding. The TLS Cipher Suite Registry provides a reference table of all registered Cipher Suite names, sorted by encoding value from small to large.

\n

Since the computation intensity of asymmetric encryption algorithms (RSA, etc.) is much higher than that of symmetric encryption algorithms (AES, etc.), practical applications almost always use symmetric encryption algorithms to encrypt messages in batches in terms of performance.

\n
\n

TLS 1.2 protocol supports a series of cipher suites that combine the Diffie-Hellman key exchange with the RSA public key encryption algorithm. They all start with TLS_DH_RSA or TLS_DHE_RSA`. The \"E\" in DHE stands for \"Ephemeral\", which means that a random \\((a,b)\\) value is required to be generated for each session. So TLS_DHE_RSA cipher suite can provide forward secrecy, while TLS_DH_RSA cannot, and the former should be preferred in practical applications.

\n

Here we take a typical TLS_DHE_RSA_WITH_AES_128_CBC_SHA (encoding 0x00,0x33) cipher suite as an example to explain the process of Diffie-Hellman working with RSA to establish a DTLS session. First, explain the composition of the cipher suite.

\n
    \n
  • DHE: ephemeral DH to implement key exchange
  • \n
  • RSA: public key for signing and certifying the DHE
  • \n
  • AES_128_CBC: 128-bit CBC mode AES encryption
  • \n
  • SHA: 160-bit HMAC-SHA1 hash message authentication code
  • \n
\n

Referring to the packet file dtls-dhe-rsa.pcap captured from the network port, the following handshake protocol message sequence chart can be obtained

\n
\nsequenceDiagram\n\nautonumber\nparticipant C as Client\nparticipant S as Server\nNote over C,S: Handshake Protocol\nrect rgb(230, 250, 255)\nC->>S: Client Hello (Cr, Cipher Suites))\nS-->>C: Hello Verify Request (Cookie)\nC->>S: Client Hello (Cr, Cookie, Cipher Suites)\nS-->>C: Server Hello (Sr, Cipher Suite), Certificate (Sn, Se)\nS-->>C: Server Key Exchange (p,g,A,Ss)\nS-->>C: Certificate Request, Server Hello Done\nC->>S: Certificate (Cn, Ce)\nC->>S: Client Key Exchange (B)\nC->>S: Certificate Verify (Cs)\nend\nNote over C,S: Establish Secure Channel\nrect rgb(239, 252, 202)\nC->>S: Change Cipher Spec, Encrypted Handshake Message\nS-->>C: Change Cipher Spec, Encrypted Handshake Message\nC->>S: Application Data\nS-->>C: Application Data\nend\n \n
\n

Below is the analysis with regard to the data package numbers in the message sequence chart:

\n
    \n
  • Packets \\(\\require{enclose}\\enclose{circle}{1}-\\enclose{circle}{3}\\) present the initial handshake message exchange.\n
      \n
    • The client first sends a Hello message containing a random number \\(C_r\\) and a list of supported cipher suites
    • \n
    • The server responds with a Hello Verify Request message containing a block of information (cookie)
    • \n
    • The client receives the Hello Verify Request and resends the Hello message with the entire contents of the previous message plus a copy of the cookie
    • \n
  • \n
\n

Hello verification is specific to DTLS to prevent denial of service attacks. The protocol stipulates that the server will not continue to serve the client until it receives a hello message containing the copied cookie.

\n
\n
    \n
  • Packets \\(\\require{enclose}\\enclose{circle}{4}-\\enclose{circle}{6}\\) shows the server enters verification and key exchange stage:\n
      \n
    • The server responds with a Hello message first, which contains the random number \\(S_r\\) and the selected cipher suite\n
        \n
      • As shown below, the server selects TLS_DHE_RSA_WITH_AES_128_CBC_SHA!
      • \n
    • \n
    • The same packet also contains the Server Certificate message, which is typically large and divided into multiple fragments
    • \n
    • The server certificate provides the RSA public key \\((S_N,\\;S_e)\\) that verifies its signature
    • \n
    • Next, the server sends a Key Exchange message containing its DH public key \\((p,g,A)\\) and signature \\(Ss\\)\n
        \n
      • The length of \\(p\\) in the figure below is 256 bytes, which means that the key length is 2048 bits and \\(Pubkey\\) is \\(A\\).
      • \n
      • You can also see in the figure that the algorithms chosen for the signature are SHA512 and RSA.
      • \n
      • The operation is to first compute \\(\\operatorname{SHA512}(Cr,Sr,p,g,A)\\) and then encrypt it with the server RSA private key
      • \n
    • \n
    • After that, the server sends a Certificate Request message and a Hello Done message\n
        \n
      • The server requests the client to send an RSA public key certificate that verifies its signature
      • \n
    • \n
  • \n
\n

Note: If DH-RSA cipher suite is used, the server-side DH public key parameters \\((p,g,A)\\) are unchanged and will be included directly in its certificate message. At this time, the server will not issue a Key Exchange message \\(\\require{enclose}\\enclose{circle}{5}\\). For DHE-RSA, the \\(A\\) value is different for each session.

\n
\n
    \n
  • Packets \\(\\require{enclose}\\enclose{circle}{7}-\\enclose{circle}{9}\\) shows the client enters verification and key echange stage:\n
      \n
    • The client first sends a Certificate message, which contains the RSA public key \\((C_N,\\;C_e)\\) and also splits into multiple fragments
    • \n
    • The client then sends a Key Exchange message, which contains its DH public key \\(B\\)\n
        \n
      • The \\(Pubkey\\) in the following figure is \\(B\\)
      • \n
    • \n
    • The client finally sends a Certificate Verify message, which contains the signature \\(Cs\\)\n
        \n
      • The signature covers all previous messages except for the initial Client Hello \\(\\require{enclose}\\enclose{circle}{1}\\) and the Hello Verify Request \\(\\require{enclose}\\enclose{circle}{2}\\)
      • \n
      • The signature operation also computes SHA512 and encrypts it with the client's RSA private key
      • \n
    • \n
  • \n
  • Packets \\(\\require{enclose}\\enclose{circle}{10}-\\enclose{circle}{11}\\) completes handshake and establishs the secure channel:\n
      \n
    • Each side first verifies the signature sent by the other side
    • \n
    • After successful verification, DH algorithm is run to generate the same premaster key
    • \n
    • Both parties call pseudo-random function (PRF) to generate a 48-byte master key from the premaster key \\[master\\_secret = \\operatorname{PRF}(pre\\_master\\_secret,\\unicode{x201C}master\\;secret\\unicode{x201D},Cr+Sr)[0..47]\\]
    • \n
    • Both parties call PRF again to generate a 72-byte key block from the master key \\[key\\_block = \\operatorname{PRF}(master\\_secret,\\unicode{x201C}key\\;expansion\\unicode{x201D},Sr+Cr)[0..71]\\]
    • \n
    • Key blocks are assigned to HMAC-SHA1 and AES_128_CBC function blocks.\n
        \n
      • Client Write Message Authentication Code (MAC) key: 20 bytes
      • \n
      • Server Write Message Authentication Code (MAC) key: 20 bytes
      • \n
      • Client Write Encryption Key: 16 bytes
      • \n
      • Server write encryption key: 16 bytes
      • \n
      \nNote that TLS/DTLS 1.2 specifies that this cipher suite uses an explicit initial vector (IV) and does not require the allocation of a key block
    • \n
    • The client generates a Change Cipher Spec message indicating the start of the encryption and MAC modules
    • \n
    • The client invokes PRF a third time to generate the 12-byte end-of-handshake authentication code used for master key and handshake message authentication, which is packaged into an end-of-handshake message and entered into the encryption and MAC modules \\[\\operatorname{PRF}(master\\_secret,finished\\_label,\\operatorname{SHA256}(handshake\\_messages))[0..11]\\]
    • \n
    • The client sends the Change Cipher Spec message and the encrypted end-of-handshake message to the server
    • \n
    • The server verifies the received client end-of-handshake message and repeats the above three steps to generate its own Change Cipher Spec message and encrypted an end-of-handshake message, then send them to the client
    • \n
    • The client completes the handshake by verifying the received server end-of-handshake message. Now the encrypted secure channel is established
    • \n
  • \n
  • Packets \\(\\require{enclose}\\enclose{circle}{12}-\\enclose{circle}{13}\\) shows that the encrypted application data exchange has officially started
  • \n
\n

This is the complete process of establishing a secure message channel using the TLS_DHE_RSA_WITH_AES_128_CBC_SHA (encoding 0x00,0x33) cipher suite, where DHE implements a key exchange with forward secrecy protection and RSA digital signature provides authentication for DHE, creating a solution for secure communication. With a clear understanding of this, we will better grasp the working mechanism of Diffie-Hellman and RSA, effectively apply them in practice and avoid unnecessary mistakes.

\n","categories":["Study Notes"],"tags":["Cryptography","Network Security"]},{"title":"Understand Endianness","url":"/en/2021/12/24/Endianness/","content":"

The problem of Endianness is essentially a question about how computers store large numbers.

\n

I do not fear computers. I fear lack of them.
Isaac Asimov (American writer and professor of biochemistry, best known for his hard science fiction)

\n
\n

We know that one basic memory unit can hold one byte, and each memory unit has its address. For an integer larger than decimal 255 (0xff in hexadecimal), more than one memory unit is required. For example, 4660 is 0x1234 in hexadecimal and requires two bytes. Different computer systems use different methods to store these two bytes. In our common PC, the least-significant byte 0x34 is stored in the low address memory unit and the most-significant byte 0x12 is stored in the high address memory unit. While in Sun workstations, the opposite is true, with 0x34 in the high address memory unit and 0x12 in the low address memory unit. The former is called Little Endian and the latter is Big Endian.

\n

How can I remember these two data storing modes? It is quite simple. First, remember that the addresses of the memory units we are talking about are always arranged from low to high. For a multi-byte number, if the first byte in the low address you see is the least-significant byte, the system is Little Endian, where Little matches low. On the contrary is Big Endian, where Big corresponds to \"high\".

\n

Program Example

\n

To deepen our understanding of Endianness, let's look at the following example of a C program:

\n
char a = 1; \t \t \t 
char b = 2;
short c = 255;\t/* 0x00ff */
long d = 0x44332211;
\n

On Intel 80x86 based systems, the memory content corresponding to variables a, b, c, and d are shown in the following table:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Address OffsetMemory Content
0x000001 02 FF 00
0x000411 22 33 44
\n

We can immediately tell that this system is Little Endian. For a 16-bit integer short c, we see the least-significant byte 0xff first, and the next one is 0x00. Similarly for a 32-bit integer long d, the least-significant byte 0x11 is stored at the lowest address 0x0004. If this is in a Big Endian computer, memory content would be 01 02 00 FF 44 33 22 11.

\n

At the run time all computer processors must choose between these two Endians. The following is a shortlist of processor types with supported Endian modes:

\n
    \n
  • Pure Big Endian: Sun SPARC, Motorola 68000, Java Virtual Machine
  • \n
  • Bi-Endian running Big Endian mode: MIPS with IRIX, PA-RISC, most Power and PowerPC systems
  • \n
  • Bi-Endian running Little Endian mode: ARM, MIPS with Ultrix, most DEC Alpha, IA-64 with Linux
  • \n
  • Little Endian: Intel x86, AMD64, DEC VAX
  • \n
\n

How to detect the Endianess of local system in the program? The following function can be called for a quick check. If the return value is 1, it is Little Endian, else Big Endian

\n
int test_endian() {
int x = 1;
return *((char *)&x);
}
\n

Network Order

\n

Endianness is also important for computer communications. Imagine that when a Little Endian system communicates with a Big Endian system, the receiver and sender will interpret the data completely differently if not handled properly. For example, for the variable d in the C program segment above, the Little Endian sender sends 11 22 33 44 four bytes, which the Big Endian receiver converts to the value 0x11223344. This is very different from the original value. To solve this problem, the TCP/IP protocol specifies a special \"network byte order\" (referred to as \"network order\"), which means that regardless of the Endian supported by the computer system, the most-significant byte is always sent first while transmitting data. From the definition, we can see that the network order corresponds to the Big Endian.

\n

To avoid communication problems caused by Endianness and to facilitate software developers to write portable programs, some C preprocessing macros are defined for conversion between network bytes and local byte order. htons() and htonl() are used to convert local byte order to network byte order, the former works with 16-bit unsigned numbers and the latter for 32-bit unsigned numbers. ntohs() and ntohl() implement the conversion in the opposite direction. The prototype definitions of these four macros can be found as follows (available in the netinet/in.h file on Linux systems).

\n
#if defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN)

#define htons(A) (A)
#define htonl(A) (A)
#define ntohs(A) (A)
#define ntohl(A) (A)

#elif defined(LITTLE_ENDIAN) && !defined(BIG_ENDIAN)

#define htons(A) ((((uint16)(A) & 0xff00) >> 8) | \\
(((uint16)(A) & 0x00ff) << 8))
#define htonl(A) ((((uint32)(A) & 0xff000000) >> 24) | \\
(((uint32)(A) & 0x00ff0000) >> 8) | \\
(((uint32)(A) & 0x0000ff00) << 8) | \\
(((uint32)(A) & 0x000000ff) << 24))
#define ntohs htons
#define ntohl htohl

#else

#error "Either BIG_ENDIAN or LITTLE_ENDIAN must be #defined, but not both."

#endif
\n","categories":["Study Notes"],"tags":["C/C++ Programming","System Programming","Computer Architecture","Computer Communications"]},{"title":"The Inductive Proof and Applications of Fermat's Little Theorem","url":"/en/2023/11/14/Fermats-Little-Theorem/","content":"

In the history of mathematics, Pierre de Fermat was a special figure. His formal occupation was as a lawyer, but he was exceptionally fond of mathematics. Although an amateur, Fermat’s achievements in mathematics were no less than those of professional mathematicians of the same era. He contributed to modern calculus, analytic geometry, probability, and number theory. Especially in the field of number theory, Fermat was most interested and achieved the most outstanding results.

\n

Logic is the foundation of the certainty of all the knowledge we acquire.
Leonhard Euler (Swiss mathematician, physicist, astronomer, geographer, logician, and engineer, one of the greatest mathematicians in history)

\n
\n

As the \"king of amateur mathematicians\", Fermat proposed some famous conjectures in number theory but did not give strong proof. The most famous is Fermat's Last Theorem1. Although Fermat claimed he had found an ingenious proof, there was not enough space on the margin to write it down. But in fact, after more than 350 years of unremitting efforts by mathematicians, it was not until 1995 that British mathematician Andrew John Wiles and his student Richard Taylor published a widely recognized proof.

\n
\n\"Ferma
Ferma and Fermat's Last Theorem On Stamp
\n
\n

In contrast, there is also a little theorem of Fermat. In October 1640, Fermat first wrote down words equivalent to the following in a letter to a friend:

\n
\n

If \\(p\\) is a prime and \\(a\\) is any integer not divisible by \\(p\\), then \\(a^{p-1}-1\\) is divisible by \\(p\\).

\n
\n

Similarly, Fermat did not give proof in the letter. Nearly a hundred years later, the complete proof was first published by the great mathematician Euler in 1736. Later, people found in the unpublished manuscripts of another great mathematician Leibniz that he had obtained almost the same proof before 1683.

\n

Fermat's little theorem is one of the fundamental results of elementary number theory. This theorem can be used to generate primality testing rules and corresponding verification algorithms. In the late 1970s, public key cryptography emerged, and Fermat's little theorem helped prove the correctness of RSA. Afterward, researchers combined it with the Chinese remainder theorem and also discovered an optimized method for RSA decryption and signing. The following further introduces these applications.

\n

Theorem and Corollaries

\n

The complete statement of Fermat's little theorem is: If \\(\\pmb{p}\\) is a prime number, then for any integer \\(\\pmb{a}\\), the number \\(\\pmb{a^p−a}\\) is an integer multiple of \\(\\pmb{p}\\). In the notation of modular arithmetic, this is expressed as \\(\\pmb{a^p\\equiv a\\pmod p}\\). If \\(\\pmb{a}\\) is not divisible by \\(\\pmb{p}\\), then \\(\\pmb{a^{p-1}\\equiv 1\\pmod p}\\).

\n

From \\(a^{p-1}\\equiv 1\\pmod p\\) it can be deduced that \\(\\pmb{a^{p-2}\\equiv a^{-1}\\pmod p}\\). This new congruence just gives a way to find the multiplicative inverse of \\(a\\) modulo \\(p\\). This is a direct corollary of Fermat's little theorem.

\n

Another important corollary is: If \\(\\pmb{a}\\) is not a multiple of \\(\\pmb{p}\\) and \\(\\pmb{n=m\\bmod {(p-1)}}\\), then \\(\\pmb{a^n\\equiv a^m\\pmod p}\\). This inference does not seem very intuitive, but the proof is simple:

\n
    \n
  1. Because \\(n=m\\bmod {(p-1)}\\), it follows that \\(m = k⋅(p-1)+n\\)
  2. \n
  3. Substituting the result into the power operation, \\(a^m=a^{k⋅(p-1)+n}=(a^{(p-1)})^k⋅a^n\\)
  4. \n
  5. Then applying modular arithmetic and Fermat's little theorem, \\(a^m=(a^{(p-1)})^k⋅a^n\\equiv (1)^ka^n\\equiv a^n\\pmod p\\)
  6. \n
  7. Therefore \\(a^n\\equiv a^m\\pmod p\\), Q.E.D.
  8. \n
\n

Proof by Induction

\n

There are many ways to prove Fermat's little theorem. Among them, mathematical induction based on the binomial theorem is the most intuitive one. First, for \\(a=1\\), it is obvious that \\(1^p \\equiv 1\\pmod{p}\\) holds. Now assume that for an integer \\(a\\), \\(a^p \\equiv a \\pmod{p}\\) is true. As long as it is proved under this condition that \\((a+1)^p\\equiv a+1\\pmod{p}\\), the proposition holds.

\n

According to the binomial theorem, \\[(a+1)^p = a^p + {p \\choose 1} a^{p-1} + {p \\choose 2} a^{p-2} + \\cdots + {p \\choose p-1} a + 1\\] Here the binomial coefficient is defined as \\({p \\choose k}= \\frac{p!}{k! (p-k)!}\\). Note that because \\(p\\) is a prime number, for \\(1≤k≤p-1\\), each binomial coefficient \\({p \\choose k}\\)is a multiple of \\(p\\).

\n

Then taking \\(\\bmod p\\), all the intermediate terms disappear, leaving only \\(a^p+1\\) \\[(a+1)^p \\equiv a^p + 1 \\pmod{p}\\]Referring to the previous assumption \\(a^p ≡ a \\pmod p\\), it infers that \\((a+1)^p \\equiv a+1 \\pmod{p}\\), the proof is complete.

\n

Applications of the Theorem

\n

Solution to Math Competition Problems

\n

Fermat's little theorem provides concise solutions to some seemingly complicated computational problems. First look at a simple example: If today is Sunday, what day will it be in \\(2^{100}\\) days? There are 7 days in a week. According to Fermat's little theorem, we have \\(2^{7−1}≡1\\bmod 7\\), from which we can get \\[2^{100}=2^{16×6+4} ≡ 1^{16}×2^4≡16≡2\\pmod 7\\]So the answer is Tuesday. This actually repeats the proof process of the second corollary above with specific numbers. Applying this corollary can greatly speed up modular exponentiation. For example, to calculate \\(49^{901}\\bmod 151\\), since \\(901\\bmod(151-1)=1\\), it can be deduced immediately that \\[49^{901}\\equiv 49^1\\equiv 49\\pmod {151}\\]

\n

Now look at a question that seems a little more difficult: Given the equation \\(133^5+110^5+84^5+27^5=n^{5}\\), find the value of \\(n\\).

\n

At first glance, there seems to be no clue, so start with basic parity checking. The left side of the equation has two odd terms and two even terms, so the total is even, which also determines that \\(n\\) must be even. Looking at the exponent 5 which is a prime number, and thinking of Fermat's little theorem, we get \\(n^5≡n\\pmod 5\\), therefore \\[133^5+110^5+84^5+27^5≡n\\pmod 5\\] \\[3+0+4+2≡4≡n\\pmod 5\\] Continuing to take modulo 3, according to the corollary of Fermat's little theorem again, we have \\(n^5≡n^{5\\mod(3-1)}≡n\\pmod 3\\). So \\[133^5+110^5+84^5+27^5≡n\\pmod 3\\] \\[1+2+0+0≡0≡n\\pmod 3\\]

\n

Okay, now summarize:

\n
    \n
  1. \\(n\\) should be greater than 27 and an even number
  2. \n
  3. \\(n\\) is a multiple of 3, so the sum of all digits is a multiple of 3
  4. \n
  5. \\(n\\) divided by 5 gives a remainder of 4, the ones place should be 4 (9 does not satisfy the condition of an even number)
  6. \n
\n

These lead to \\(n = 144\\) or \\(n\\geq 174\\). Obviously, 174 is too big. It can be concluded that n can only be 144.

\n

This question actually appeared in the 1989 American Invitational Mathematics Examination (AIME), which is a math competition for high school students. Interestingly, the solution to the question happens to disprove Euler's conjecture.

\n

Primality Testing

\n

Many encryption algorithm applications require \"random\" large prime numbers. The common method to generate large primes is to randomly generate an integer and then test for primality. Since Fermat’s little theorem holds on the premise that p is a prime number, this provides a prime test method called the Fermat primality test. The test algorithm is

\n
\n

Input: \\(n\\) - the number to be tested, \\(n>3\\); \\(k\\) - the number of iterations
\nOutput: \\(n\\) is composite, otherwise may be prime
\nRepeat k times:
\n\\(\\quad\\quad\\)Randomly select an integer \\(a\\) between \\([2, n-2]\\)
\n\\(\\quad\\quad\\)If \\(a^{n-1}\\not \\equiv 1{\\pmod n}\\), return \\(n\\) is composite
\nReturn \\(n\\) may be prime

\n
\n

It can be seen that Fermat’s primality test is non-deterministic. It uses a probabilistic algorithm to determine whether a number is composite or probably prime. When the output is composite, the result is definitely correct; but those numbers tested to be probably prime may actually be composite, such numbers are called Fermat pseudoprimes. The smallest Fermat pseudoprime is 341, with \\(2^{340}\\equiv1\\pmod {341}\\) but \\(341=11×31\\). So in fact, Fermat's little theorem provides a necessary but insufficient condition for determining prime numbers. It can only be said that the more iterations performed, the higher the probability that the tested number is prime.

\n

There is also a class of Fermat pseudoprimes \\(n\\) which are composite numbers themselves, but for any integer \\(x\\) that is coprime with \\(n\\), it holds \\(x^{n-1}\\equiv 1\\pmod n\\). In number theory, they are called Carmichael numbers. The smallest Carmichael number is 561, equal to \\(3×11×17\\). Carmichael numbers can fool Fermat’s primality test, making the test unreliable. Fortunately, such numbers are very rare. Statistics show that among the first \\(10^{12}\\) natural numbers there are only 8241 Carmichael numbers.

\n

The PGP encryption communication program uses Fermat’s primality test in its algorithm. In network communication applications requiring large primes, Fermat’s primality test method is often used for pretesting, followed by calling the more efficient Miller-Rabin primality test to ensure high accuracy.

\n

Proof of RSA Correctness

\n

Fermat's little theorem can also be used to prove the correctness of the RSA algorithm, that is, the decryption formula can completely restore the plaintext \\(m\\) from the ciphertext \\(c\\) without errors: \\[c^d=(m^{e})^{d}\\equiv m\\pmod {pq}\\]

\n

Here \\(p\\) and \\(q\\) are different prime numbers, \\(e\\) and \\(d\\) are positive integers that satisfy \\(ed≡1\\pmod {λ(pq)}\\), where \\(λ(pq)=\\mathrm{lcm}(p−1,q−1)\\). \\(\\mathrm{lcm}\\) is the least common multiple function.

\n

Before starting the proof, first introduce a corollary of the Chinese remainder theorem: If integers \\(\\pmb{n_1,n_2,...,n_k}\\) are pairwise coprime and \\(\\pmb{n=n_{1}n_{2}...n_{k}}\\), then for any integer \\(\\pmb x\\) and \\(\\pmb y\\), \\(\\pmb{x≡y\\pmod n}\\) holds if and only if \\(\\pmb{x≡y\\pmod{n_i}}\\) for each \\(\\pmb{i=1,2,...k}\\). This corollary is easy to prove, details are left as an exercise2. According to this corollary, if \\(m^{ed}≡m\\pmod p\\) and \\(m^{ed}≡m\\pmod q\\) are both true, then \\(m^{ed}≡m\\pmod{pq}\\) must also hold.

\n

Now look at the first step of the proof. From the relationship between \\(e\\) and \\(d\\), it follows \\(ed-1\\) can be divided by both \\(p-1\\) and \\(q-1\\), that is, there exist non-negative integers \\(h\\) and \\(k\\) satisfying: \\[ed-1=h(p-1)=k(q-1)\\]

\n

The second step is to prove \\(m^{ed}≡m\\pmod p\\). Consider two cases:

\n
    \n
  1. If \\(m≡ 0\\pmod p\\), i.e. \\(m\\) is an integer multiple of \\(p\\), then naturally \\(m^{ed}≡0≡m\\pmod p\\)
  2. \n
  3. If \\(m\\not \\equiv 0\\pmod p\\), it can be deduced that: \\[m^{ed}=m^{ed-1}m=m^{h(p-1)}m=(m^{p-1})^{h}m\\equiv 1^{h}m\\equiv m{\\pmod {p}}\\]Here Fermat’s little theorem \\(m^{p−1}≡1\\pmod p\\) is applied.
  4. \n
\n

The third step has the goal of proving \\(m^{ed}≡m\\pmod q\\). The deduction process is similar to the previous step, and it can also be deduced that m^ed ≡ m (mod q):

\n
    \n
  1. If \\(m≡ 0\\pmod p\\), i.e. \\(m\\) is an integer multiple of \\(q\\), then naturally \\(m^{ed}≡0≡m\\pmod q\\)
  2. \n
  3. If \\(m\\not \\equiv 0\\pmod q\\), it can be deduced that: \\[m^{ed}=m^{ed-1}m=m^{h(q-1)}m=(m^{q-1})^{h}m\\equiv 1^{h}m\\equiv m{\\pmod {q}}\\]
  4. \n
\n

Since both \\(m^{ed}≡m\\pmod p\\) and \\(m^{ed}≡m\\pmod q\\) have been proved, \\(m^{ed}≡m\\pmod{pq}\\) holds, Q.E.D.

\n

Optimized RSA Decryption

\n

Combining Fermat’s little theorem and the Chinese remainder theorem can not only verify the correctness of the RSA but also deduce an optimized decryption method.

\n

In the RSA encryption algorithm, the modulus \\(N\\) is the product of two prime numbers \\(p\\) and \\(q\\). Therefore, for any number \\(m\\) less than \\(N\\), letting \\(m_1=m\\bmod p\\) and \\(m_2=m\\bmod q\\), \\(m\\) is uniquely determined by \\((m_1,m_2)\\). According to the Chinese remainder theorem, we can use the general solution formula to deduce \\(m\\) from \\((m_1,m_2)\\). Since \\(p\\) and \\(q\\) each have only half the number of bits as \\(N\\), modular arithmetic will be more efficient than directly computing \\(c^d\\equiv m\\pmod N\\). And in the process of calculating \\((m_1,m_2)\\), applying the corollary of Fermat's little theorem yields: \\[\\begin{align}\nm_1&=m\\bmod p=(c^d\\bmod N)\\bmod p\\\\\n&=c^d\\bmod p=c^{d\\mod(p-1)}\\bmod p\\tag{1}\\label{eq1}\\\\\nm_2&=m\\bmod q=(c^d\\bmod N)\\bmod q\\\\\n&=c^d\\bmod q=c^{d\\mod(q-1)}\\bmod q\\tag{2}\\label{eq2}\\\\\n\\end{align}\\]

\n

Obviously, in above \\((1)\\) and \\((2)\\) the exponent \\(d\\) is reduced to \\(d_P=d\\bmod (p-1)\\) and \\(d_Q=d\\bmod (q-1)\\) respectively, which further speeds up the calculation. Finally, the step of calculating \\(m\\) is further optimized using the Garner algorithm3: \\[\\begin{align}\nq_{\\text{inv}}&=q^{-1}\\pmod {p}\\\\\nh&=q_{\\text{inv}}(m_{1}-m_{2})\\pmod {p}\\\\\nm&=m_{2}+hq\\pmod {pq}\\tag{3}\\label{eq3}\n\\end{align}\\] Note that given \\((p,q,d)\\), the values of \\((d_P,d_Q,q_\\text{inv})\\) are determined. So they can be precomputed and stored. For decryption, only \\((m_1,m_2,h)\\) are to be calculated and substituted into the above (3).

\n

This is actually the decryption algorithm specified in the RSA cryptography standard RFC 8017 (PKCS #1 v2.2). The ASN.1 formatted key data sequence described by this specification corresponds exactly to the above description (\\(d_P\\) - exponent1,\\(d_Q\\) - exponent2,\\(q_{\\text{inv}}\\) - coefficient):

\n
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
\n

The widely used open-source library OpenSSL implements this efficient and practical decryption algorithm. As shown below, the key data generated using the OpenSSL command line tool is consistent with the PKCS #1 standard:

\n
# Generate 512-bit RSA keys saved in PEM format file.
# For demo only, DON'T USE 512-bit KEYS IN PRODUCTION!
$ openssl genrsa -out private-key.pem 512
Generating RSA private key, 512 bit long modulus
.++++++++++++
......................++++++++++++
e is 65537 (0x10001)

# Inspect RSA keys saved in a PEM format file.
$ openssl pkey -in private-key.pem -text
-----BEGIN PRIVATE KEY-----
MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEA7HwgswSjqvDRPWj3
vVIxMZDAtXJCa7Qx+2jFv7e7GXB8+fa3MTBL36YjIcAgLeCHAyIzWkPndxvTJE2l
WvYzRQIDAQABAkBCUp2pF0f/jQJhwqqYQhDh4cLqIF1Yb3UFGWE8X37tpwCifAqg
t8NEpaXWkct5M+YxqjKfdOKYy0TVcJRlyS+RAiEA9xujHmh+bOvl0xWDFoARDAHw
v94qRCpeRNveHFpNvPsCIQD0/qFpeSjRWj/4vjCkIOv1RbbhDHVsgsF9HRJNW2Rc
vwIgaGIAUcQKQ7CScMxRh5upl8zqCeKrMAhFsgi+lnN/CykCIDMdAL4Jmht7ccdK
nslPWQs1/T6co878xLN+ojfjbl/vAiEAhmp4YDX1g8kFh6cVtTIDT5AGtzqwB2Jw
cCq+IoKDYBc=
-----END PRIVATE KEY-----
Private-Key: (512 bit)
modulus:
00:ec:7c:20:b3:04:a3:aa:f0:d1:3d:68:f7:bd:52:
31:31:90:c0:b5:72:42:6b:b4:31:fb:68:c5:bf:b7:
bb:19:70:7c:f9:f6:b7:31:30:4b:df:a6:23:21:c0:
20:2d:e0:87:03:22:33:5a:43:e7:77:1b:d3:24:4d:
a5:5a:f6:33:45
publicExponent: 65537 (0x10001)
privateExponent:
42:52:9d:a9:17:47:ff:8d:02:61:c2:aa:98:42:10:
e1:e1:c2:ea:20:5d:58:6f:75:05:19:61:3c:5f:7e:
ed:a7:00:a2:7c:0a:a0:b7:c3:44:a5:a5:d6:91:cb:
79:33:e6:31:aa:32:9f:74:e2:98:cb:44:d5:70:94:
65:c9:2f:91
prime1:
00:f7:1b:a3:1e:68:7e:6c:eb:e5:d3:15:83:16:80:
11:0c:01:f0:bf:de:2a:44:2a:5e:44:db:de:1c:5a:
4d:bc:fb
prime2:
00:f4:fe:a1:69:79:28:d1:5a:3f:f8:be:30:a4:20:
eb:f5:45:b6:e1:0c:75:6c:82:c1:7d:1d:12:4d:5b:
64:5c:bf
exponent1:
68:62:00:51:c4:0a:43:b0:92:70:cc:51:87:9b:a9:
97:cc:ea:09:e2:ab:30:08:45:b2:08:be:96:73:7f:
0b:29
exponent2:
33:1d:00:be:09:9a:1b:7b:71:c7:4a:9e:c9:4f:59:
0b:35:fd:3e:9c:a3:ce:fc:c4:b3:7e:a2:37:e3:6e:
5f:ef
coefficient:
00:86:6a:78:60:35:f5:83:c9:05:87:a7:15:b5:32:
03:4f:90:06:b7:3a:b0:07:62:70:70:2a:be:22:82:
83:60:17
\n
\n
\n
    \n
  1. Also known as \"Fermat's conjecture\",its gist is that, when \\(n > 2\\), the equation \\(x^{n}+y^{n}=z^{n}\\) has no positive integer solutions \\((x, y, z)\\). After it was finally proven correct in 1995, it became known as \"Fermat's last theorem.\"↩︎

  2. \n
  3. Hint: If two integers are congruent modulo \\(n\\), then \\(n\\) is a divisor of their difference.↩︎

  4. \n
  5. Garner, H., \"The Residue Number System\", IRE Transactions on Electronic Computers, Volume EC-8, Issue 2, pp.140-147, DOI 10.1109/TEC.1959.5219515, June 1959↩︎

  6. \n
\n
\n","categories":["Study Notes"],"tags":["Cryptography"]},{"title":"IPv4 and IPv6 Header Checksum Algorithm Explained","url":"/en/2021/12/26/IPv4-IPv6-checksum/","content":"

About the IP packet header checksum algorithm, simply put, it is 16-bit ones' complement of the ones' complement sum of all 16-bit words in the header. However, not many sources show exactly how this is done. The same checksum algorithm is used by TCP segment and UDP datagram, but the data involved in the checksum computing is different from that in the IP header. In addition, the checksum operation of the IPv6 packet is different from that of IPv4. Therefore, it is necessary to make a comprehensive analysis of the checksum algorithm of IP packets.

\n

Nothing in life is to be feared, it is only to be understood.
Marie Curie (Polish and naturalized-French physicist and chemist, twice Nobel Prize winner)

\n
\n

IPv4 Header Checksum

\n

IPv4 packet header format can be seen below

\n
0                   1                   2                   3    
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\n

Here the 16-bit Header Checksum field is used for error-checking of the IPv4 header. While computing the IPv4 header checksum, the sender first clears the checksum field to zero, then calculates the sum of each 16-bit value within the header. The sum is saved in a 32-bit value. If the total number of bytes is odd, the last byte is added separately.

\n

After all additions, the higher 16 bits saving the carry is added to the lower 16 bits. Repeat this till all higher 16 bits are zeros. Finally, the sender takes the ones' complement of the lower 16 bits of the result and writes it to the IP header checksum field.

\n

The following demonstrates the entire calculation process using actual captured IPv4 packets.

\n
0x0000: 00 60 47 41 11 c9 00 09 6b 7a 5b 3b 08 00 45 00 
0x0010: 00 1c 74 68 00 00 80 11 59 8f c0 a8 64 01 ab 46
0x0020: 9c e9 0f 3a 04 05 00 08 7f c5 00 00 00 00 00 00
0x0030: 00 00 00 00 00 00 00 00 00 00 00 00
\n

At the beginning of the above 16-bit hex dump is the Ethernet frame header. The IP packet header starts from offset 0x000e, with the first byte 0x45 and the last byte 0xe9. Based on the previous description of the algorithm, we can make the following calculations:

\n
(1) 0x4500 + 0x001c + 0x7468 + 0x0000 + 0x8011 +
0x0000 + 0xc0a8 + 0x6401 + 0xab46 + 0x9ce9 = 0x3a66d
(2) 0xa66d + 0x3 = 0xa670
(3) 0xffff - 0xa670 = 0x598f
\n

Notice at step (1) we replace the checksum field with 0x0000. As can be seen, the calculated header checksum 0x598f is the same as the value in the captured packet. This calculating process is only used for the sender to generate the initial checksum. In practice, for the intermediate forwarding router and the final receiver, they can just sum up all header fields of the received IP packet by the same algorithm. If the result is 0xffff, the checksum verification passes.

\n

C Program Implementation

\n

How to program IPv4 header checksum computing? RFC 1071 (Computing the Internet Checksum) shows a reference \"C\" language implementation:

\n
{
/* Compute Internet Checksum for "count" bytes
* beginning at location "addr".
*/
register long sum = 0;

while( count > 1 ) {
/* This is the inner loop */
sum += * (unsigned short *) addr++;
count -= 2;
}

/* Add left-over byte, if any */
if ( count > 0 )
sum += * (unsigned char *) addr;

/* Fold 32-bit sum to 16 bits */
while (sum>>16)
sum = (sum & 0xffff) + (sum >> 16);

checksum = ~sum;
}
\n

In a real network connection, the source device can call the above code to generate the initial IPv4 header checksum. This checksum is then updated at each step of the routing hop because the router must decrement the Time To Live (TTL) field. RFC 1141 (Incremental Updating of the Internet Checksum) gives a reference implementation of fast checksum update:

\n
unsigned long sum;
ipptr->ttl--; /* decrement ttl */
sum = ipptr->Checksum + 0x100; /* increment checksum high byte*/
ipptr->Checksum = (sum + (sum>>16)); /* add carry */
\n

TCP/UDP Header Checksum

\n

For TCP segment and UDP datagram, both have 16-bit header checksum fields used for error-checking by the destination host. The checksum computing algorithm is the same as the IP header, except for the difference of covered data. Here the checksum is calculated over the whole TCP/UDP header and the payload, plus a pseudo-header that mimics the IPv4 header as shown below:

\n
 0      7 8     15 16    23 24    31 
+--------+--------+--------+--------+
| source address |
+--------+--------+--------+--------+
| destination address |
+--------+--------+--------+--------+
| zero |protocol| TCP/UDP length |
+--------+--------+--------+--------+
\n

It consists of the source and destination IP addresses, the protocol number (TCP:6/UDP:17), and the total length of the TCP/UDP header and payload (in bytes). The purpose of including the pseudo-header in the checksum computing is to confirm the packet reaches the expected destination and avoid IP spoofing attacks. Besides, for IPv4 UDP header checksum is optional, it carries all-zeros if unused.

\n

IPv6 Difference

\n

IPv6 is IP protocol version 6, and its main design goal was to resolve the problem of IPv4 address exhaustion. Of course, it provides many benefits in other aspects. Although IPv6 usage is growing slowly, the trend is unstoppable. The latest IPv6 standard is published in RFC 8200(Internet Protocol, Version 6 (IPv6) Specification).

\n

IPv6 packet header format can be seen below

\n
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| Traffic Class | Flow Label |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Length | Next Header | Hop Limit |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Source Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Destination Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\n

Notice that the IPv6 header does not include a checksum field, a significant difference from IPv4. The absence of a checksum in the IPv6 header furthers the end-to-end principle of Internet design, to simplify router processing and speed up the packet transmission. Protection for data integrity can be accomplished by error detection at the link layer or the higher-layer protocols between endpoints (such as TCP/UDP on the transport layer). This is why IPv6 forces the UDP layer to set the header checksum.

\n

For IPv6 TCP segment and UDP datagram header checksum computing, the pseudo-header that mimics the IPv6 header is shown below

\n
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Source Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Destination Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Upper-Layer Packet Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| zero | Next Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\n

UDP-Lite Application

\n

In actual IPv6 network applications, UDP-Lite (Lightweight UDP) can be used to balance error detection and transmission efficiency. UDP-Lite has its own protocol number 136, and its standard is described in RFC 3828 (The Lightweight User Datagram Protocol (UDP-Lite)).

\n

Referring to the following header format, UDP-Lite uses the same set of port number values assigned by the IANA for use by UDP. But it redefines the Length field in the UDP header to a Checksum Coverage, which allows the application layer to control the length of checksummed data. This is useful for the application that can be tolerant of the potentially lossy transmission of the uncovered portion of the data.

\n
 0              15 16             31
+--------+--------+--------+--------+
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+
| Checksum | |
| Coverage | Checksum |
+--------+--------+--------+--------+
| |
: Payload :
| |
+-----------------------------------+
\n

UDP-Lite protocol defines the values of \"Checksum Coverage\" (in bytes) as shown in the following table:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Checksum CoverageCoverage AreaDescription
0entire UDP-Lites datagramCalculation covers IP pseudo-header
1-7(invalid)The receiver has to drop the datagram
8UDP-Lites headerCalculation covers IP pseudo-header
> 8UDP-Lites header + portion of payload dataCalculation covers IP pseudo-header
> IP datagram length(invalid)The receiver has to drop the datagram
\n

For multimedia applications running VoIP or streaming video data transmission protocols, it'd better receive data with some degree of corruption than not receiving any data at all. Another example is the CAPWAP protocol used to connect Cisco wireless controller and access points. It specifies UDP-Lite as the default transport protocol for the CAPWAP Data channel, while the connection is established over the IPv6 network.

\n

At last, share a C program snippet to present how to initialize a Berkeley socket to establish an IPv6 UDP-Lite connection:

\n
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/udplite.h>

int udplite_conn = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDPLITE);
int val = 8; /* checksum only covers 8-byte UDP-Lite header */
(void)setsockopt(udplite_conn, IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV, &val, sizeof val);
(void)setsockopt(udplite_conn, IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV, &val, sizeof val);
\n

Here IPPROTO_UDPLITE is protocol number 136, which is used together with AF_INET6 address family parameter in socket() function call for IPv6 socket creation. The UDPLITE_SEND_CSCOV(10) and UDPLITE_RECV_CSCOV(11) are the control parameters of socket options configuration function setsockopt(), used for setting the Checksum Coverage value in the sender and the receiver respectively. Remember that both the sender and the receiver must set the same value, otherwise, the receiver will not be able to verify the checksum properly.

\n","categories":["Study Notes"],"tags":["C/C++ Programming","TCP/IP"]},{"title":"IPv6 Dynamic Address Allocation Mechanism Illustrated","url":"/en/2022/03/13/IPv6-Addressing/","content":"

IPv6 supports multiple addresses, making address assignments more flexible and convenient. Unlike IPv4, which relied solely on the DHCP protocol for address assignment, IPv6 incorporates a native Stateless Address AutoConfiguration SLAAC) protocol. SLAAC can either work alone to provide IPv6 addresses to hosts, or it can work with DHCPv6 to generate new assignment schemes. Here is a comprehensive analysis of the dynamic address allocation mechanism for IPv6.

\n

Who the hell knew how much address space we needed?
Vint Cerf (American Internet pioneer and one of \"the fathers of the Internet\")

\n
\n

IPv6 Address Overview

\n

Address Formats

\n

The most significant difference between IPv6 and IPv4 is its large address space. IPv4 has 32 bits (4 bytes) and allows for approximately 4.29 (232) billion addresses. IPv6, on the other hand, defines 128 bits (16 bytes) and supports approximately 340 x 1036 addresses. This is a pretty impressive number, and there will be no address depletion for the foreseeable future. A typical IPv6 address can be divided into two parts. As shown in the figure below, the first 64 bits are used to represent the network, and the next 64 bits are used as the interface identifier.

\n

The interface identifier can be generated in several ways:

\n\n

IETF recommends a canonical textual representation format for ease of writing. It includes leading zeros suppression and compression of consecutive all-zero fields. With the network prefix length at the end, the above address can be shortened to 2001:db8:130f::7000:0:140b/64.

\n

Address Types

\n

RFC 4291 defines three types of addresses:

\n
    \n
  1. Unicast: A network address corresponds to a single network node, point-to-point connection.
  2. \n
  3. Anycast: The target address corresponds to a group of receiving nodes, but only the \"nearest\" one receives.
  4. \n
  5. Multicast: The target address corresponds to a group of nodes that can receive replicated messages.
  6. \n
\n

Note that there are no broadcast addresses in IPv6, their function being superseded by multicast addresses. Anycast addresses are syntactically indistinguishable from unicast addresses and have very limited applications. A typical application for anycast is to set up a DNS root server to allow hosts to look up domain names in close proximity. For unicast and multicast addresses, they can be identified by different network prefixes:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Address TypeBinary FormHexadecimal FormApplication
Link-local address (unicast)1111 1110 10fe80::/10Use on a single link, non-routable
Unique local address (unicast)1111 1101fd00::/8Analogous to IPv4 private network addressing
Global unicast address0012000::/3Internet communications
Multicast address1111 1111ff00::/8Group communications, video streaming
\n

Each interface of a host must have a link-local address. Additionally, it can be manually or dynamically autoconfigured to obtain a unique local address and a global unicast address. Thus, IPv6 interfaces naturally have multiple unicast addresses. Unique local addresses are managed by the local network administrator, while the global unicast addresses are allocated by the IANA-designated regional registry. Referring to the following diagram, all current global unicast addresses are assigned from the 2000::/3 address block, with the first 48 bits of the address identifying the service provider's global routing network and the next 16 bits identifying the enterprise or campus internal subnet: Because an IPv6 multicast address can only be used as a destination address, its bit definition is different from that of unicast. Referring to RFC 4291, a multicast address containing 4 bits of the feature flags, 4 bits of the group scope, and the last 112 bits of the group identifier: Furthermore the same protocol specifies a few pre-defined IPv6 multicast addresses, the most important of which are

\n
    \n
  • All Nodes Addresses on the local link — ff02::1
  • \n
  • All Routers Addresses on the local link — ff02::2
  • \n
  • Solicited-Node Address on local link — ff02::1:ffxx:xxxx
  • \n
\n

Dynamic Allocation Schemes

\n

NDP Protocol

\n

IPv6 dynamic address assignment depends on Neighbor Discovery Protocol (NDP). NDP acts at the data link layer and is responsible for discovering other nodes and corresponding IPv6 addresses on the link and determining available routes and maintaining information reachability to other active nodes. It provides the IPv6 network with the equivalent of the Address Resolution Protocol (ARP) and ICMP router discovery and redirection protocols in IPv4 networks. However, NDP adds many improvements and new features. NDP defines five ICMPv6 message types:

\n
    \n
  1. Router Solicitation (RS)
  2. \n
  3. Router Advertisement (RA)
  4. \n
  5. Neighbor Solicitation (NS)
  6. \n
  7. Neighbor Advertisement (NA)
  8. \n
  9. Redirect
  10. \n
\n

The first two message types here, RS and RA, are the keys to implementing dynamic IPv6 address assignment. The host sends an RS message to the multicast address ff02::2 of all routers in the local network segment to request routing information. When the router receives the RS from the network node, it sends an immediate RA in response. The message format of the RA is as follows

\n
 0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cur Hop Limit |M|O| Reserved | Router Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reachable Time |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Retrans Timer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options ...
+-+-+-+-+-+-+-+-+-+-+-+-
\n

It defines two special bits, M and O, with the following meaning:

\n
    \n
  • M — \"Managed address configuration\" flag, set to 1 when the address is obtained from DHCPv6.
  • \n
  • O — \"Other configuration\" flag, set to 1 to indicate that other configuration information is available via DHCPv6
  • \n
\n

The RA message ends with the Options section, which originally had three possible options: Source Link-Layer Address, MTU, and Prefix Information. Later, RFC 8106 (which replaced RFC 6106) added the Recursive DNS Server (RDNSS) and DNS Search List (DNSSL) options. The Prefix Information option directly provide hosts with on-link prefixes and prefixes for Address Autoconfiguration, and it has the following format

\n
 0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Prefix Length |L|A| Reserved1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Valid Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Preferred Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reserved2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Prefix +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\n

Here the Prefix Length and the Prefix jointly determine the network prefix of the IPv6 address. In addition, the Prefix Information option also defines two special bits, L and A:

\n
    \n
  • L — on-link flag. When set, indicates that this prefix can be used for on-link determination.
  • \n
  • A — autonomous address-configuration flag. When set, indicates that this prefix can be used for SLAAC.
  • \n
\n

Similar to the IPv4 subnet mask feature, the purpose of the \"on-link\" determination is to allow the host to determine which networks an interface can access. By default, the host only considers the network where the link-local address is located as \"on-link\". If the \"on-link\" status of a destination address cannot be determined, the host forwards the IPv6 datagram to the default gateway (or default router) by default. When the host receives an RA message, if the \"on-link\" flag for a prefix information option is set to 1 and the Valid Lifetime is also a non-zero value, the host creates a new prefix network entry for it in the prefix list. All unexpired prefix network entries are \"on-link\".

\n

Message Sequence

\n

After understanding the NDP protocol and the information conveyed by the RA messages, let's see how they guide the network nodes to achieve dynamic address assignment.

\n

Routers in the network periodically send RA messages to the multicast addresses (ff02::1) of all nodes in the local subnet. However, to avoid latency, the host sends one or more RS messages to all routers in the local subnet as soon as it has finished booting. The protocol requires the routers to respond to the RA messages within 0.5 seconds. Then, based on the values of the M/O/A bits in the received RA messages, the host decides how to dynamically configure the unique local and global unicast addresses of the interface and how to obtain other configuration information. With certain combinations of bit fetch values, the host needs to run DHCPv6 client software to connect to the server to obtain address assignment and/or other configuration information. The entire process is shown in the following message sequence diagram.

\n
\nsequenceDiagram\n\nparticipant R as Router\nparticipant H as Host\nparticipant S as DHCPv6 Server\nNote over R,H: Router Request\nrect rgb(239, 252, 202)\nH->>R: Router Solicitation\nR-->>H: Router Advertisement\nend\nNote over H,S: Address Request\nrect rgb(230, 250, 255)\nH->>S: DHCPv6 Solicit\nS-->>H: DHCPv6 Advertise\nH->>S: DHCPv6 Request\nS-->>H: DHCPv6 Reply\nend\nNote over H,S: Other Information Request\nrect rgb(230, 250, 255)\nH->>S: DHCPv6 Information-request\nS-->>H: DHCPv6 Reply\nend\n\n
\n

Note: Unlike the IPv4 DHCP protocol, DHCPv6 clients use UDP port 546 and servers use UDP port 547.

\n
\n

Next explain in detail three dynamic allocation schemes determined by the combination of the M/O/A-bit values:

\n
    \n
  • SLAAC
  • \n
  • SLAAC + Stateless DHCPv6
  • \n
  • Stateful DHCPv6
  • \n
\n

SLAAC

\n

SLAAC is the simplest automatic IPv6 address assignment scheme and does not require any server. It works by sending an RS message request after the host starts up and the router sends back RA messages to all nodes in the local network segment. If the RA message contains the following configuration

\n
    \n
  • M-bit and O-bit all clear in the message header
  • \n
  • L-bit and A-bit all set in Prefix Information option
  • \n
\n

Then the host receives this RA message and performs the following operations to implement SLAAC:

\n
    \n
  1. Combine the network prefix with the local interface identifier to generate a unique local address or global unicast address.
  2. \n
  3. Install the default gateway (or default route) to point to the router address (source address of the RA message).
  4. \n
  5. Set this interface as the \"on-link\" corresponding to the network prefix, which is also the next-hop interface of the default gateway above.
  6. \n
  7. If the RDNSS and/or DNSSL options are included, install the name servers and domain name suffixes.
  8. \n
\n

This way, the host gets one or more IPv6 unique local addresses or global unicast addresses, plus the default gateway and domain name service information to complete various Internet connections.

\n

The following is an example of the SLAAC configuration on a Cisco Catalyst 9300 Multilayer Access Switch:

\n
ipv6 unicast-routing
interface Vlan10
ipv6 enable
ipv6 address 2001:ABCD:1000::1/64
ipv6 nd ra dns server 2001:4860:4860::8888 infinite
ipv6 nd ra dns search-list example.com
\n

The Layer 3 interface of the Cisco Multilayer Switch provides routing functionality. As you can see, when IPv6 is activated on the Layer 3 interface in VLAN 10, its default address auto-assignment scheme is SLAAC. the control bits of RA messages from this interface are all set according to the SLAAC scheme, and the network prefixes for each IPv6 address it configures are automatically added to the RA prefix information options list. Of course, the network administrator can also exclude certain network prefixes with a separate interface configuration command. The last two lines of the example configuration command specify RDNSS and DNSSL, which are also added to the RA message options.

\n

If a host connects to a port in VLAN 10, it immediately gets a global unicast address with the network prefix of 2001:ABCD:1000::/64, and its default gateway address is set to 2001:ABCD:1000::1. Open a browser and enter a URL, and it will send a message to the specified domain name server 2001:4860:4860::8888 (Google's public name server address) to obtain the IPv6 address of the destination URL to establish a connection.

\n

SLAAC + Stateless DHCPv6

\n

SLAAC automatic address assignment is fast and easy, providing a plug-and-play IPv6 deployment solution for small and medium-sized network deployments. However, if a network node needs access to additional configuration information, such as NTP/SNTP server, TFTP server, and SIP server addresses, or if its functionality relies on certain Vendor-specific Information Options, it must choose SLAAC + stateless DHCPv6 scheme.

\n

This scenario still uses SLAAC automatic address assignment, but the router instructs the host to connect to a DHCPv6 server for additional configuration information. At this point, the RA message sent back by the router has

\n
    \n
  • M-bit clear and O-bit set in the message header
  • \n
  • L-bit and A-bit all set in Prefix Information option
  • \n
\n

After receiving this RA message, the host performs the following actions:

\n
    \n
  1. Combine the network prefix with the local interface identifier to generate a unique local address or global unicast address.
  2. \n
  3. Install a default gateway (or default route) pointing to the router address (source address of the RA message).
  4. \n
  5. Set this interface as the \"on-link\" corresponding to the network prefix, which is also the next-hop interface of the default gateway above.
  6. \n
  7. If the RDNSS and/or DNSSL options are included, install the name servers and domain name suffixes.
  8. \n
  9. Start the DHCPv6 client and connect to the DHCPv6 server to request additional configuration information.
  10. \n
  11. Save the additional configuration information replied by the DHCPv6 server.
  12. \n
\n

As you can see, SLAAC + stateless DHCPv6 is not different from SLAAC in terms of address assignment. DHCPv6 only provides additional configuration information and does not assign IPv6 addresses. So the DHCPv6 server does not track the address assignment status of network nodes, which is what \"stateless\" means.

\n

The corresponding configuration commands on the Catalyst 9300 switch are as follows.

\n
ipv6 unicast-routing
ipv6 dhcp pool vlan-10-clients
dns-server 2001:4860:4860::8888
domain-name example.com
sntp address 2001:DB8:2000:2000::33
interface Vlan10
ipv6 enable
ipv6 address 2001:ABCD:1000::1/64
ipv6 nd other-config-flag
ipv6 dhcp server vlan-10-clients
# ipv6 dhcp relay destination 2001:9:6:40::1
\n

The difference with the SLAAC example is that the VLAN 10 interface configuration command ipv6 nd other-config-flag explicitly specifies to set the O-bit of the RA message. Its next command, ipv6 dhcp server vlan-10-clients, activates the DHCPv6 server response feature of the interface, corresponding to the server's pool name of vlan-10-clients. The DHCPv6 server is configured above the interface configuration, starting at ipv6 dhcp pool vlan-10-clients, and contains the DNS server address, DNS domain name, and SNTP server address.

\n

If you are using a separate DHCPv6 server located on a network segment, you can remove the ipv6 dhcp server command and enable the ipv6 dhcp relay destination command on the next line of the example to specify the address to forward DHCPv6 requests to the external server.

\n

Stateful DHCPv6

\n

Many large enterprises use DHCP to manage the IPv4 addresses of their devices, so deploying DHCPv6 to centrally assign and manage IPv6 addresses is a natural preference. This is where Stateful DHCPv6 comes into play. This scenario also requires RA messages sent by the router but does not rely solely on network prefixes for automatic address assignment. The control bits of the RA messages are configured to

\n
    \n
  • M-bit set in the message header, O-bit does not matter
  • \n
  • L-bit and A-bit can be set or clear as desired in Prefix Information option
  • \n
\n

Upon receiving this RA message, the host performs the following actions:

\n
    \n
  1. Generate a unique local address or a global unicast address if there is a Prefix Information option with the A-bit set.
  2. \n
  3. Install a default gateway (or default route) pointing to the router address (source address of the RA message).
  4. \n
  5. If there is a Prefix Information option with the L-bit set, set this interface to \"on-link\" with the corresponding network prefix.
  6. \n
  7. If the RDNSS and/or DNSSL options are included, install the name servers and domain suffixes.
  8. \n
  9. Start the DHCPv6 client and connect to the server to request addresses and other configuration information.
  10. \n
  11. Set the address assigned by the DHCPv6 server to this interface.
  12. \n
  13. Save additional configuration information from the DHCPv6 server response.
  14. \n
\n

An example of the Stateful DHCPv6 configuration command on a Catalyst 9300 switch is as follows.

\n
ipv6 unicast-routing
ipv6 dhcp pool vlan-10-clients
address prefix FD09:9:5:90::/64
address prefix 2001:9:5:90::/64
dns-server 2001:9:5:90::115
domain-name test.com
interface Vlan10
ipv6 enable
ipv6 address 2001:ABCD:1:1::1/64
ipv6 nd prefix 2001:ABCD:1:1::/64 no-advertise
ipv6 nd managed-config-flag
ipv6 dhcp server vlan-10-clients
\n

Compared to SLAAC + Stateless DHCPv6, the interface configuration here removes the ipv6 nd other-config-flag and replaces it with the ipv6 nd managed-config-flag command. This corresponds to setting the M-bit of the RA message header. The DHCPv6 server configuration adds two address prefix commands to set the network prefix. Also, the ipv6 nd prefix 2001:ABCD:1:1::/64 no-advertise configured for the interface specifies that the router does not include the 2001:ABCD:1:1::/64 prefix information option into the RA. So, this example host interface will not generate SLAAC addresses, but only two addresses from DHPCv6: a unique local address with the network prefix FD09:9:5:90::/64, and a global unicast address with the network prefix 2001:9:5:90::/64. The interface identifier for each of these two addresses is also specified by DHPCv6.

\n

How to distinguish the source of dynamically assigned addresses for host interfaces? The method is simple. One thing to remember is that DHPCv6 does not send the network prefix length to the requestor, so the network prefix length of the addresses received from DHPCv6 is 128, while the network prefix length of the addresses generated by SLAAC will not be 128. See the following example of the wired0 interface on a Linux host:

\n
ifconfig wired0
wired0 Link encap:Ethernet HWaddr A0:EC:F9:6C:D9:30
inet6 addr: 2001:20::53c7:1364:a4d8:fd91/128 Scope:Global
inet6 addr: 2001:20::a2ec:f9ff:fe6c:d930/64 Scope:Global
inet6 addr: fe80::a2ec:f9ff:fe6c:d930/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:510 errors:0 dropped:0 overruns:0 frame:0
TX packets:1213 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:93670 (91.4 KiB) TX bytes:271979 (265.6 KiB)
\n

We can immediately determine that the interface is using Stateful DHCPv6 address assignment, but also generates the SLAAC address with the same network prefix 2001:20::/64 received.

\n
    \n
  • 2001:20::53c7:1364:a4d8:fd91/128 — DHCPv6 address, random interface identifer
  • \n
  • 2001:20::a2ec:f9ff:fe6c:d930/64 — SLAAC addeess, interface identifer is MAC in EUI-64 format
  • \n
  • fe80::a2ec:f9ff:fe6c:d930/64 — Link-local address, interface identifer is MAC in EUI-64 format
  • \n
\n

Note: DHPCv6 server also does not provide any IPv6 default gateway information. The host needs to be informed of the dynamic default gateway from the RA message.

\n
\n

Summary and Comparison

\n

The following table shows the control bit combinations of RA messages concerning different address allocation and other configuration acquisition methods.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
M-bitO-bitA-bitHost AddressOther Configuration
000Static SettingsManual Configuration
001Prefix specified by RA, automatically generatedmanually configured
010Static SettingsDHCPv6
011Prefix specified by RA, automatically generatedDHCPv6
100Stateful DHCPv6DHCPv6
101Stateful DHCPv6 and/or automatically generatedDHCPv6
110Stateful DHCPv6DHCPv6
111Stateful DHCPv6 and/or automatically generatedDHCPv6
\n

Summarize three dynamic allocation schemes:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Allocation SchemeFeaturesAppiccation Scenarios
SLAACSimple and practical, fast deploymentSMB, Consumer Product Networking, Internet of Things (IoT)
SLAAC + Stateless DHCPv6Auto Configuration, Extended ServicesSMBs need additional network services
Stateful DHCPv6Centralized management and controlLarge enterprises, institutions, and campus networks
\n

Note: Since IPv6 network interfaces can have multiple addresses (a link-local address, plus one or more unique local addresses and/or global unicast addresses), it becomes important how the source address is selected when establishing an external connection. RFC 6724 gives detailed IPv6 source address selection rules. In the development of embedded systems, the control plane and the data plane connected to the same remote device are often implemented by different functional components. For example, the control plane directly calls a Linux userspace socket to establish the connection, and the IPv6 source address used for the connection is selected by the TCP/IP stack, while the data plane directly implements data encapsulation processing and transmission in kernel space. In this case, the IPv6 source address selected by the control plane has to be synchronized to the data plane in time, otherwise, the user data might not be delivered to the same destination.

\n
\n

Troubleshooting Guide

\n

The common IPv6 dynamic address assignment debugging and troubleshooting commands on Cisco routers and switches are listed in the following table.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
CommandDescription
show ipv6 interface briefDisplays a short summary of IPv6 status and configuration for each interface
show ipv6 interface [type] [num]Displays IPv6 and NDP usability status information for single interface
show ipv6 interface [type] [num] prefixDisplays IPv6 network prefix information for single interface
show ipv6 dhcp poolDisplay DHCPv6 configuration pool information
show ipv6 dhcp bindingDisplays all automatic client bindings from the DHCPv6 server binding table
show ipv6 dhcp interface [type] [num]Display DHCPv6 interface information
debug ipv6 ndDebug IPv6 NDP protocol
debug ipv6 dhcpDebug DHCPv6 server
\n

The following console NDP protocol debug log shows that the router received an RS message from host FE80::5850:6D61:1FB:EF3A and responded with an RA message to the multicast address FF02::1 of all nodes in this network:

\n
Router# debug ipv6 nd
ICMP Neighbor Discovery events debugging is on
Router# show logging | include RS
ICMPv6-ND: Received RS on GigabitEthernet0/0/0 from FE80::5850:6D61:1FB:EF3A
Router# show logging | include RA
ICMPv6-ND: Sending solicited RA on GigabitEthernet0/0/0
ICMPv6-ND: Request to send RA for FE80::C801:EFFF:FE5A:8
ICMPv6-ND: Setup RA from FE80::C801:EFFF:FE5A:8 to FF02::1 on GigabitEthernet0/0/0
\n

And the next log shows an example of Stateless DHCPv6 observed after entering the debug ipv6 dhcp debug command. Host FE80::5850:6D61:1FB:EF3A sends an INFORMATION-REQUEST message to the DHCPv6 server, which selects the source address FE80::C801:B9FF:FEF0:8 and sends a response message.

\n
Router#debug ipv6 dhcp
IPv6 DHCP debugging is on

IPv6 DHCP: Received INFORMATION-REQUEST from FE80::5850:6D61:1FB:EF3A on FastEthernet0/0
IPv6 DHCP: Option VENDOR-CLASS(16) is not processed
IPv6 DHCP: Using interface pool LAN_POOL
IPv6 DHCP: Source Address from SAS FE80::C801:B9FF:FEF0:8
IPv6 DHCP: Sending REPLY to FE80::5850:6D61:1FB:EF3A on FastEthernet0/0
\n

The following debug log of Stateful DHCPv6 shows the complete process of two message exchanges (SOLICIT/ADVERTISE, REQUEST/REPLY) on lines 1, 15, 16, and 26.

\n
IPv6 DHCP: Received SOLICIT from FE80::5850:6D61:1FB:EF3A on FastEthernet0/0
IPv6 DHCP: Option UNKNOWN(39) is not processed
IPv6 DHCP: Option VENDOR-CLASS(16) is not processed
IPv6 DHCP: Using interface pool LAN_POOL
IPv6 DHCP: Creating binding for FE80::5850:6D61:1FB:EF3A in pool LAN_POOL
IPv6 DHCP: Binding for IA_NA 0E000C29 not found
IPv6 DHCP: Allocating IA_NA 0E000C29 in binding for FE80::5850:6D61:1FB:EF3A
IPv6 DHCP: Looking up pool 2001:ABCD::/64 entry with username '000100011F3E8772000C29806CCC0E000C29'
IPv6 DHCP: Poolentry for the user not found
IPv6 DHCP: Allocated new address 2001:ABCD::D9F7:61C:D803:DCF1
IPv6 DHCP: Allocating address 2001:ABCD::D9F7:61C:D803:DCF1 in binding for FE80::5850:6D61:1FB:EF3A, IAID 0E000C29
IPv6 DHCP: Updating binding address entry for address 2001:ABCD::D9F7:61C:D803:DCF1
IPv6 DHCP: Setting timer on 2001:ABCD::D9F7:61C:D803:DCF1 for 60 seconds
IPv6 DHCP: Source Address from SAS FE80::C801:B9FF:FEF0:8
IPv6 DHCP: Sending ADVERTISE to FE80::5850:6D61:1FB:EF3A on FastEthernet0/0
IPv6 DHCP: Received REQUEST from FE80::5850:6D61:1FB:EF3A on FastEthernet0/0
IPv6 DHCP: Option UNKNOWN(39) is not processed
IPv6 DHCP: Option VENDOR-CLASS(16) is not processed
IPv6 DHCP: Using interface pool LAN_POOL
IPv6 DHCP: Looking up pool 2001:ABCD::/64 entry with username '000100011F3E8772000C29806CCC0E000C29'
IPv6 DHCP: Poolentry for user found
IPv6 DHCP: Found address 2001:ABCD::D9F7:61C:D803:DCF1 in binding for FE80::5850:6D61:1FB:EF3A, IAID 0E000C29
IPv6 DHCP: Updating binding address entry for address 2001:ABCD::D9F7:61C:D803:DCF1
IPv6 DHCP: Setting timer on 2001:ABCD::D9F7:61C:D803:DCF1 for 172800 seconds
IPv6 DHCP: Source Address from SAS FE80::C801:B9FF:FEF0:8
IPv6 DHCP: Sending REPLY to FE80::5850:6D61:1FB:EF3A on FastEthernet0/0
\n

For complex cases where it is difficult to identify whether the problem is with the host, router, or DHCPv6 server, we recommend using the free open-source network packet analysis software Wireshark to capture packets of the entire process for analysis. While analyzing packets with Wireshark, you can apply the keyword filtering function.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Filter StringOnly Show
icmpv6.type=133ICMPv6 RS
icmpv6.nd.ra.flagICMPv6 RA
dhcpv6DHCPv6 packets
\n

We can either run Wireshark directly on the host side, or we can use the Switched Port Analyzer (SPAN) provided with the switch. Running on the network side, SPAN can collectively redirect packets from a given port to the monitor port running Wireshark for capturing. Cisco Catalyst 9300 Series switches also directly integrate with Wireshark software to intercept and analyze filtered packets online, making it very easy to use.

\n

Sample packet capture files for three allocation scheme are available here for download and study: slaac.pcapstateless-dhcpv6.pcapstateful-dhcpv6.pcap

\n

References

\n

IPv6 Product Certification Test

\n

Accurate and effective testing of IPv6 products is key to ensuring high interoperability, security, and reliability of IPv6 infrastructure deployments. The IPv6 Ready logo is an IPv6 testing and certification program created by the IPv6 Forum. Its goals are to define IPv6 conformance and interoperability test specifications, provide a self-testing toolset, establish Global IPv6 Test Centers and provide product validation services, and finally, issue IPv6 Ready logo.

\n

In May 2020, IPv6 Ready Logo Program published new version 5.0 test specifications

\n
    \n
  • IPv6 Core Protocols Test Specification (Conformance)
  • \n
  • IPv6 Core Protocols Interoperability Test Specification (Interoperability)
  • \n
\n

Along with these two new test specifications, the project team also affirmed two permanent changes:

\n
    \n
  1. Testing must be done in an IPv6-only environment, without any IPv4 being used for the device to function.
  2. \n
  3. The device under test must have IPv6 on and enabled on all IP interfaces by default.
  4. \n
\n

Not surprisingly, the new version 5.0 core protocols test specification has a section dedicated to defining SLAAC test cases to validate this core IPv6 protocol.

\n

IPv6 Core Protocol RFC List

\n

In the list below, the RFCs shown in bold are directly covered by the IPv6 Ready Version 5.0 Core Protocol Test Specification:

\n
    \n
  • RFC 4191 Default Router Preferences and More-Specific Routes
  • \n
  • RFC 4193 Unique Local IPv6 Unicast Addresses
  • \n
  • RFC 4291 IP Version 6 Addressing Architecture
  • \n
  • RFC 4443 Internet Control Message Protocol (ICMPv6) for the Internet Protocol Version 6 (IPv6) Specification
  • \n
  • RFC 4861 Neighbor Discovery for IP version 6 (IPv6)
  • \n
  • RFC 4862 IPv6 Stateless Address Autoconfiguration
  • \n
  • RFC 4941 Privacy Extensions for Stateless Address Autoconfiguration in IPv6
  • \n
  • RFC 5095 Deprecation of Type 0 Routing Headers in IPv6
  • \n
  • RFC 6724 Default Address Selection for Internet Protocol Version 6 (IPv6)
  • \n
  • RFC 6980 Security Implications of IPv6 Fragmentation with IPv6 Neighbor Discovery
  • \n
  • RFC 7217 A Method for Generating Semantically Opaque Interface Identifiers with IPv6 Stateless Address Autoconfiguration (SLAAC)
  • \n
  • RFC 8064 Recommendation on Stable IPv6 Interface Identifiers
  • \n
  • RFC 8106 IPv6 Router Advertisement Options for DNS Configuration
  • \n
  • RFC 8200 Internet Protocol, Version 6 (IPv6) Specification
  • \n
  • RFC 8201 Path MTU Discovery for IP version 6
  • \n
  • RFC 8415 Dynamic Host Configuration Protocol for IPv6 (DHCPv6)
  • \n
\n","categories":["Study Notes"],"tags":["TCP/IP","Cisco Technology"]},{"title":"Purdue CS24000 Fall 2018 Midterm I Solutions","url":"/en/2024/02/24/Purdue-CS240-2018-Fall-Midterm1/","content":"

Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solution and study notes for the Fall 2018 Midterm 1 exam.

\n

CS24000 Syllabus

\n

Below are extracted from the Spring 2024 CS24000 course syllabus:

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n\n

\n
    \n
  • Reference: Beej’s Guide to C Programming; Brian “Beej” Hall; 2007
  • \n
  • Course Outcomes: A student who successfully fulfills the course requirements will have the ability to:\n
      \n
    • write quality code that is readable, maintainable, and well commented
    • \n
    • create, compile, and execute C programs using industry standard tools including the GNU Compiler Collection
    • \n
    • apply debugging techniques to analyze, identify, and fix errors
    • \n
    • assess and address security-related issues in code bases written in C
    • \n
    • produce code that appropriately and properly utilizes pointers
    • \n
    • solve problems through the application of explicit memory management
    • \n
    • design and implement programs in C that utilize dynamic data structures such as linked lists and trees
    • \n
  • \n
  • Lectures:
  • \n
\n

Fall 2018 Midterm 1 Exam

\n
\n

Exam Solutions and Notes

\n

Problem 1 (20 pts)

\n
    \n
  • (a) gcc -Wall -Werror -g -c abc.c -o xyz.o
    \nExplanation of the options used:

    \n
      \n
    • -Wall: Enable all warnings.
    • \n
    • -Werror: Treat warnings as errors.
    • \n
    • -g: Include debugging information in the output file.
    • \n
    • -c: Compile or assemble the source files, but do not link.
    • \n
    • abc.c: The source file to be compiled.
    • \n
    • -o xyz.o: Specify the output file name (xyz.o).
    • \n
    \n

    📝Notes: This output file xyz.o is not executable since it is just the object file for a single c source file. We need to link to the standard library to make a executable file. If we force to run this xyz.o, it will return something like exec format error.

  • \n
  • (b) gcc xyz.o abc.o def.c -o prog
    \nExplanation:

    \n
      \n
    • xyz.o, abc.o: Object files to be linked.
    • \n
    • def.c: Source file to be compiled and linked.
    • \n
    • -o prog: Specify the output file name (prog).
    • \n
  • \n
  • (c) It advises gcc to include all warnings that help detect potentially problematic code.

  • \n
  • (d) Many functions found in the string library (declared in string.h) rely on null-terminated strings to operate correctly. Null-terminated strings are sequences of characters followed by a null character ('\\0'), which indicates the end of the string. Functions like strlen, strcpy, strcat, strcmp, and others expect null-terminated strings as input and produce null-terminated strings as output.

  • \n
  • (e) In C, memory for a variable is allocated during its definition, not during its declaration.

    \n

    Declaration is announcing the properties of variable (no memory allocation), definition is allocating storages for a variable. Put pure declaration (struct, func prototype, extern) outside of the func, put definition inside func.

  • \n
  • (f) size = 32 (There are 8 integer elements in this array, so 4 * 8.)

  • \n
  • (g) 5 (Because ptr is given the address of the 3rd element. So *(ptr - 1) is the value of the 2nd element.)

  • \n
  • (h) 12 (This is equal to *(ptr - *(ptr + 3)), then *(ptr - 2). So finally it points to the 1st element of the array.)

  • \n
  • (i) 8 (Because it mentions \"64-bit architecture\", so all addresses are of size 64-bit)

  • \n
\n

Problem 2 (20 pts)

\n
    \n
  • (a) The answer is shown below: (remember to define ID_LEN first and add ';' after the '}')

    \n

    #define ID_LEN (5)

    struct resistor {
    char id[ID_LEN];
    float max_power;
    int resistance;
    };

  • \n
  • (b) The answer is shown below:

    \n

    typedef struct resistor resistor_t;

  • \n
  • (c) The answer is shown below: (remember to define ID_LEN first and add ';' after the '}')

    \n

    #define CNAME_LEN (24)

    struct circuit_struct {
    char name[CNAME_LEN];
    resistor_t resistors[10];
    };

  • \n
  • (d) It will print sizeof = 920. Explanation: 5 * (24 + 10 * (8 + 4 + 4)) = 920. This is because the id inside the resistor will occupy 8 bytes after padding to a multiple of 4.

    \n

    struct circuit_struct circuit_board[5];

  • \n
  • (e) The function can be written like the following:

    \n

    int find_voltage(resistor_t r, int c) {
    return (c * r.resistance);
    }

  • \n
\n

Problem 3 (40 pts)

\n

The complete program is shown below

\n
#include <stdio.h>

#define ID_LEN 5
#define CNAME_LEN 24

struct resistor {
char id[ID_LEN];
float max_power;
int resistance;
};

typedef struct resistor resistor_t;

struct circuit_struct {
char name[CNAME_LEN];
resistor_t resistors[10];
};

int blown_resistors(char* infile, char* outfile, float voltage) {

FILE *in = fopen(infile, "r");
if(!in) return -1;

FILE *out = fopen(outfile, "w");
if(!out) {
fclose(in);
in = NULL;
return -1;
}

// First pass - calculate total resistance
int total_resistance = 0;
int items;
char id[ID_LEN];
int resistance;
float max_power;

fseek(in, 0, SEEK_SET);

while (fscanf(in, "%[^,],%d,%f\\n", id, &resistance, &max_power) == 3) {
total_resistance += resistance;
}

if (!feof(in)) {
// Input format error
fclose(in);
in = NULL;
fclose(out);
out = NULL;
return -1;
}

// Calculate current
float current = voltage / total_resistance;

// Second pass - check for blown resistors
int blown_count = 0;

fseek(in, 0, SEEK_SET);

while (fscanf(in, "%[^,],%d,%f\\n", id, &resistance, &max_power) == 3) {
float power = current * current * resistance;
if (power > max_power) {
blown_count++;
fprintf(out, "%s, %.2f\\n", id, power);
}
}

fclose(in);
in = NULL;
fclose(out);
out = NULL;

return blown_count;
}

int main(void) {
printf("return is %d\\n", blown_resistors("input", "output", 100));
return 0;
}
\n

Problem 4 (20 pts)

\n

The solution can be like this: (the include and struct definition are not necessary)

\n
#include <stdio.h>

struct coord {
float x;
float y;
};

struct coord find_center(FILE *file_ptr) {
struct coord center = {0.0, 0.0};
struct coord temp;
int count = 0;

if (file_ptr == NULL) {
fprintf(stderr, "Error: NULL file pointer\\n");
return center;
}

// Set file position indicator to the beginning
rewind(file_ptr);

while (fread(&temp, sizeof(struct coord), 1, file_ptr) == 1) {
center.x += temp.x;
center.y += temp.y;
count++;
}

if (count > 0) {
center.x /= count;
center.y /= count;
}

return center;
}
\n","categories":["Study Notes"],"tags":["C/C++ Programming"]},{"title":"Purdue CS24000 Fall 2018 Midterm II Solutions","url":"/en/2024/03/27/Purdue-CS240-2018-Fall-Midterm2/","content":"

Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solution and study notes for the Fall 2018 Midterm 2 exam.

\n

CS24000 Syllabus

\n

Below are extracted from the Spring 2024 CS24000 course syllabus:

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n\n

\n
    \n
  • Reference: Beej’s Guide to C Programming; Brian “Beej” Hall; 2007
  • \n
  • Course Outcomes: A student who successfully fulfills the course requirements will have the ability to:\n
      \n
    • write quality code that is readable, maintainable, and well commented
    • \n
    • create, compile, and execute C programs using industry standard tools including the GNU Compiler Collection
    • \n
    • apply debugging techniques to analyze, identify, and fix errors
    • \n
    • assess and address security-related issues in code bases written in C
    • \n
    • produce code that appropriately and properly utilizes pointers
    • \n
    • solve problems through the application of explicit memory management
    • \n
    • design and implement programs in C that utilize dynamic data structures such as linked lists and trees
    • \n
  • \n
  • Lectures:
  • \n
\n

Fall 2018 Midterm 2 Exam

\n
\n

Exam Solutions and Notes

\n

Problem 1 (30 pts)

\n

(a) Code without using array brackets:

\n
int reverse(int *source, int *dest, int n) {
int sum = 0;
int* srcptr = source;
int* dstptr = dest;

for (int i = 0; i < n; i++) {
*(dstptr + i) = *(srcptr + n - 1 - i);
sum += *(dstptr + i);
}
return sum;
}}
\n

In summary, the reverse function reverses the order of elements in the source array, stores them in the dest array, and calculates the sum of the reversed elements.

\n

(b) The atomic weight of Aluminum is 26.981.

\n

(c) Structure for a singly-linked list node containing an integer:

\n
typedef struct single_node {
int data;
struct single_node *next;
} single_node_t;
\n

(d) Function to prepend a node to a singly-linked list:

\n
void push(single_node_t **head, single_node_t *node) {
assert(head != NULL);
assert(node != NULL);
assert(node->next == NULL);
node->next = *head;
*head = node;
return;
}
\n

(e) Function to remove the first node from a singly-linked list:

\n
single_node_t *pop(single_node_t **head) {
assert(head != NULL);
if (*head == NULL) {
return NULL;
}

single_node_t *tmp = *head;
*head = (*head)->next;
return tmp;
}
\n

Problem 2 (40 pts)

\n

(a) Structure for a doubly-linked list node containing a string and an integer:

\n
typedef struct double_node {
char *name;
int age;
struct double_node *prev;
struct double_node *next;
} double_node_t;
\n

(b) Function to create a new doubly-linked list node:

\n
double_node_t *create(char *name, int age) {
double_node_t *new_node = malloc(sizeof(double_node_t));
if (new_node == NULL) {
// Handle memory allocation failure
return NULL;
}

unsigned_int name_len = strlen(name) + 1;
new_node->name = malloc(name_len * sizeof(char));
if (new_node->name == NULL) {
// Handle memory allocation failure
free(new_node);
return NULL;
}

strcpy(new_node->name, name);
new_node->age = age;
new_node->prev = NULL;
new_node->next = NULL;
return new_node;
}
\n

(c) Function to delete a node from a doubly-linked list:

\n
void delete(double_node_t *node) {
if (node == NULL) {
return;
}

if (node->prev) {
node->prev->next = node->next;
}

if (node->next) {
node->next->prev = node->prev;
}

free(node->name);
free(node);
}
\n

(d) Function to insert a new node after a given node in a doubly-linked list:

\n
void insert(double_node_t *node, double_node_t *new_node) {
if (node == NULL || new_node == NULL) {
return;
}

new_node->prev = node;
new_node->next = node->next;

if (node->next) {
node->next->prev = new_node;
}

node->next = new_node;
}
\n

Problem 3 (30 pts)

\n

(a) Structure for a binary tree node:

\n
typedef struct tree_node {
int value;
bool invalid;
struct tree_node *left;
struct tree_node *right;
} tree_node_t;
\n

(b) The size of the tree_node_t structure on a 64-bit architecture system is 24 bytes (4 bytes for int, 1 byte for bool, and 8 bytes for each pointer).

\n

(c) Function to mark a node as invalid:

\n
void delete_node(tree_node_t *node) {
if (node) {
node->invalid = true;
}
return;
}
\n

(d) Function to remove a node from a binary tree (assuming it's not the root):

\n
void free_node(tree_node_t *node) {
if (node) {
tree_node_t *parent = get_parent(node);

// Case 1: Node has no children
if (!node->left && !node->right) {
if (parent->left == node) {
parent->left = NULL;
} else {
parent->right = NULL;
}
}
// Case 2: Node has only one child
else if (!node->left || !node->right) {
tree_node_t *child = node->left ? node->left : node->right;
if (parent->left == node) {
parent->left = child;
} else {
parent->right = child;
}
}
// Case 3: Node has two children
else {
// Find the right most child of the left child
tree_node_t *predecessor = node->left;
while (predecessor->right) {
predecessor = predecessor->right;
}

// Adjust the predecessor and its parent's children links
tree_node_t *predecessor_parent = get_parent(predecessor);
if (predecessor_parent != node) {
predecessor_parent->right = predecessor->left;
predecessor->left = node->left;
}
predecessor->right = node->right;

// Promote it as the new child of the removed node's parent
if (parent->left == node) {
parent->left = predecessor;
} else {
parent->right = predecessor;
}
}

free(node);
}
}
\n

(e) Recursive function to delete invalid nodes from a binary tree:

\n
int flush_tree(tree_node_t *root, void (*my_del)(tree_node_t *)) {
if (root == NULL) {
return 0;
}

int deleted = 0;

// recursively traverse the tree in postfix (L-R-N) fashion
deleted += flush_tree(root->left, my_del);
deleted += flush_tree(root->right, my_del);

if (root->invalid) {
my_del(root);
deleted++;
}

return deleted;
}
\n","categories":["Study Notes"],"tags":["C/C++ Programming"]},{"title":"Purdue CS24000 2022 and 2023 Summer Midterm Exam Solutions","url":"/en/2024/02/25/Purdue-CS240-2022-2023-Summer-Midterm/","content":"

Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solutions and study notes for the 2022 and 2023 Midterm exams.

\n

Introduction

\n

Below are extracted from the Summer 2023 CS24000 course homepage:

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n\n

\n
    \n
  • Reference: Beej’s Guide to C Programming; Brian “Beej” Hall; 2007
  • \n
  • Lecture Subjects\n
      \n
    • Roles of C compiler, C preprocessor, linker, loader.
    • \n
    • Main memory: addresses and their content, meaning of variables.
    • \n
    • Reading from stdin and writing to stdout.
    • \n
    • Fundamental difference between printf() and scanf(): need to pass addresses in scanf().
    • \n
    • Pointers and indirection.
    • \n
    • Global vs. local variables.
    • \n
    • Function calls and passing arguments.
    • \n
    • Passing by value vs. reference, their typical usage.
    • \n
    • Basic methods for run-time debugging.
    • \n
    • Memory layout of 1-D arrays, indexing using pointer notation.
    • \n
    • Segmentation fault, silent run-time errors.
    • \n
    • Array overrun, stack smashing and gcc intervention.
    • \n
    • Scope of global and local variables, properties of static variables.
    • \n
    • Memory layout of 2-D integer arrays, indexing using pointer notation.
    • \n
    • Basic string processing.
    • \n
    • Function pointers.
    • \n
    • Basic file I/O.
    • \n
    • Controlling the number of bytes read to prevent stack smashing.
    • \n
    • Using the make tool to help automate code maintenance.
    • \n
    • Bit processing techniques, common applications.
    • \n
    • Basic dynamic memory allocation using malloc(), 1-D and 2-D array examples.
    • \n
    • Applications of 2-D tables, limitation and caution regarding the use of variable length arrays.
    • \n
    • Command-line argument support in main(), loader invocation and passing arguments using execl().
    • \n
    • Applications of command-line arguments.
    • \n
    • Composite data types using struct, its memory structure, and applications.
    • \n
    • Conversion/casting of data types.
    • \n
    • Variadic functions: structure and applications.
    • \n
    • Application of passing function pointers: responding to events via callback functions (i.e., throwing and catching exceptions).
    • \n
    • union and enum: structure and applications.
    • \n
    • Role of const qualifier in argument passing.
    • \n
    • Basic structure of concurrent client/server apps, shell as an example app.
    • \n
    • Additional features and applications of file I/O.
    • \n
  • \n
\n

Summer 2022 Midterm Solutions and Notes

\n

Problem 1 (36 pts)

\n

(a) Consider the code snippet

\n
int a, *b, *c;
a = 3; b = &a;
printf("%d", *b);
*c = 5;
printf("%d", *c);
\n

Explain in detail what is likely to happen if the code snippet is compiled and executed.

\n

(b) What are the possible outcomes if the code snippet

\n
char r[4];
r[0] = 'H';
r[1] = 'i';
printf("%s", r);
\n

is compiled and executed? Explain your reasoning.

\n

(c) Suppose we have a 2-D array, int x[2][3], wherein 6 integers are stored. What array expression is *(*(x+1)+2) equivalent to, and why?

\n

Problem 1 Solution

\n
\n

(a) The first printf() outputs 3 since b is a pointer to integer variable a. *c = 5 is likely to generate a segmentation fault since the code does not place a valid address in c before this assignment. The second printf() is likely not reached due to a segmentation fault from *c = 5 which terminates the running program.

\n

(b) There are two possible outcomes:

\n
    \n
  1. prints \"Hi\" to stdout.
  2. \n
  3. prints \"Hi\" followed by additional byte values.
  4. \n
\n

Explanation: If the memory location r[2] contains EOS ('\\0') then the first outcome results. Otherwise, printf() will continue to print byte values (not necessarily ASCII) until a byte containing 0 (i.e.,EOS) is reached.

\n

(c) Equivalent to x[1][2].

\n

Explanation: In our logical view of 2-D arrays: x points to the location in memory where the beginning addresses of two 1-D integer arrays are located. Therefore x+1 points to the beginning address of the second 1-D integer array. *(x+1) follows the pointer to the beginning address of the second 1-D integer array. *(x+1)+2 results in the address at which the third element of the second 1-D integer array is stored. *(*(x+1)+2) accesses the content of the third element of the second 1-D integer array. Hence equivalent to x[1][2].

\n\n
\n

Problem 2 (32 pts)

\n

(a) Suppose main() calls function

\n
int abc(void) {
int a = 3, static int b = 1;
if(++a > ++b) return a++;
else return ++b;
}
\n

three times. Explain what values are returned to main() in each of the three calls to abc().

\n

(b) Suppose the code snippet

\n
float m, **n;
m = 3.3;
printf("%f", m);
**n = 5.5;
printf("%p", n);
\n

is compiled and executed. What is likely to happen, and why? How would you modify the code (involving printf() calls) to facilitate ease of run-time debugging?

\n

Problem 2 Solution

\n
\n

(a) Here are the three return values for each call and the explanation:

\n
    \n
  1. First call returns 4. The if-statement checks 4 > 2 and a++ returns 4 before incrementing a.
  2. \n
  3. Second call returns 4. Before the if-statement, the static variable b becomes 2 since it preserves the previous value from the first call. So the if-statement checks 4 > 3. Hence a++ returns 4.
  4. \n
  5. Third call return 5. Now the static variable b becomes 3 at the beginning of the call, and the if-statement checks 4 > 4. So the program goes to the else-part which increments b again and returns b. Hence the function call returns 5.
  6. \n
\n

(b) Since we did not assign a valid address to n, **n is likely to reference an invalid address that triggers a segmentation fault which terminates the running program.

\n

Although the first printf() call was successful, 3.3 will likely will not be output to stdout (i.e., display) due to abnormal termination of the program and buffering by stdio library functions.

\n

Adding a newline in the first printf() call, or calling fflush(stdout) after the first printf() call will force 3.3 in the stdout buffer to be flushed before the program terminates due to segmentation fault.

\n\n
\n

Problem 3 (32 pts)

\n

(a) Suppose you are supervising a team of C programmers. One of the team members is responsible for coding a function, int readpasswd(void), that reads from stdin a new password and checks that it contains upper case letters, special characters, etc. per company policy. The team member shows you part of the code

\n
int readpasswd() {
char secret[100];
scanf("%s", secret);
/* code follows to check validity of password */
}
\n

that reads a password from stdin and stores it in local variable secret for further processing. Explain why you would be alarmed by the code. How would you rewrite to fix the problem in the code?

\n

(b) Code main() that reads a file, test.out, byte by byte using fgetc() and counts how many bytes are ASCII characters. main() outputs the count to stdout. Focus on making sure that your code is robust and does not crash unexpectedly.

\n

Problem 3 Solution

\n
\n

(a) The scanf() does not prevent user input that exceeds buffer size (100 characters) from overwriting memory in readpasswd()'s stack frame, potentially modifying its return address. This can lead to the execution of unintended code such as malware.

\n

Alternate: The scanf() functions can lead to a buffer overflow if used improperly. Here in this function, it does not have bound checking capability and if the input string is longer than 100 characters, then the input will overflow into the adjoining memory and corrupt the stack frame.

\n

📝Notes: This is a major security flaw in scanf family (scanf, sscanf, fscanf ..etc) esp when reading a string because they don't take the length of the buffer (into which they are reading) into account.

\n

To fix this, the code should explicitly check that no more than 100 characters are read from stdin to prevent overflow over secret[100]. This can be done by reading character by character using getchar() in a loop until a newline is encountered or 100 characters have been read.

\n

(b) A sample solution can be seen below

\n
#include <stdio.h>
int main() {
FILE *fp;
int c, count;

if ((fp = fopen("test.out","r")) == NULL) {
fprintf(stderr,"opening file blog.dat failed\\n");
exit(1);
}

count = 0;
while ((c = fgetc(fp)) != EOF) {
if (0 <= c <= 127) {
// it's an ASCII character, increment count
count++;
}
}
printf("count = %d\\n", count); //output result fclose(fp);
fp = NULL;
}
\n\n
\n

Bonus Problem (10 pts)

\n

Suppose you are given the code in main.c

\n
int s[5]; 
int main() {
int i;
for (i=0; i<50; i++)
s[i] = 0;
}
\n

which is compiled using gcc and executed. What are the two possible outcomes? Explain your answer.

\n

Bonus Problem Solution

\n
\n
    \n
  • Outcome 1: The for-loop overwrites global memory following s[5] which may, or may not, corrupt program data and computation but does not crash the running program (i.e., silent run-time bug).
  • \n
  • Outcome 2: The for-loop overwrites global memory following s[5] which exceeds the running program's valid memory, resulting in a segmentation fault.
  • \n
\n\n
\n

Summer 2023 Midterm Solutions and Notes

\n

Problem 1 (30 pts)

\n

(a) Consider the code snippet

\n
int x, *y, *z;
x = 5;
y = &x;
*y = 10;
printf("%d %p\\n", x, y);
*z = 3;
\n

Explain what is likely to happen if the code snippet is compiled and executed as part of main().

\n

(b) Explain what the declarations of g and h mean:

\n
char *g(char *), (*h)(char *);
\n

For the two assignment statements to be meaningful

\n
x = g(s);
h = y;
\n

what must be the types of x and y? Provide the C statements for their type declarations.

\n

Problem 1 Solution

\n
\n

(a) printf() will output 10 (for x) and the address of x (in hexadecimal notation) which is contained in y. Assignment statement *z = 3 will likely trigger a segmentation fault since a valid address has not been stored in z.

\n

(b) g is a function that takes a single argument that is a pointer to char (i.e., char *), and g returns a pointer to char (i.e., address that points to char). h is a function pointer that takes a single argument that is a pointer to char, and h returns a value of type char.

\n

x is a pointer to char, i.e., char *x. y is a function that takes an argument that is a pointer to char and returns a value of type char, i.e., char y(char *).

\n\n
\n

Problem 2 (30 pts)

\n

(a) For the function

\n
void fun(float a) {
float x[5], i;
for (i=0; i<8; i++)
x[i] = a;
}
\n

explain what is likely to happen if fun() is called by main(). Explain how things change if 1-D array x is made to be global.

\n

(b) What are potential issues associated with code snippet

\n
FILE *f;
char r[100];
f = fopen("data.dat", "r");
fscanf(f, "%s", r);
\n

Provide modified code that fixes the issues.

\n

Problem 2 Solution

\n
\n

(a) Calling fun() will likely generate a stack smashing error. This is so since x is local to fun() and overflowing the 1-D array (by 3 elements, i.e., 12 bytes) is likely to cause the canary (bit pattern) inserted by gcc (to guard the return address) to be changed. If x is made global, gcc does not insert a canary, hence stack smashing will not occur. However, overflowing x may, or may not, trigger a segmentation fault.

\n

(b) Two potential issues:

\n
    \n
  1. fopen() may fail and return NULL.
  2. \n
  3. fscanf() may overflow 1-D array r if the character sequence in data.dat exceeds 100 bytes.
  4. \n
\n

To fix these, do the following modifications:

\n
f = fopen("data.dat", "r");
if (f == NULL) {
printf("error opening data.dat");
exit(1);
}
fscanf(f, "%99s", r);
\n\n
\n

Problem 3 (40 pts)

\n

(a) A 2-D integer array, int d[100][200], declaration is restrictive in that it hardcodes the number of rows and columns to fixed values 100 and 200, respectively. Suppose two integers N and M are read from stdin that specify the number of rows and columns of a 2-D integer array which is then used to read N x M integers from stdin into main memory. Provide C code main() that uses malloc() to achieve this task. Your code should be complete but for including header files.

\n

(b) Provide code that reads a value of type unsigned int from stdin, then uses bit processing techniques to count how many of the 32 bits contain bit value 0. Annotate your code to note what the different parts are doing.

\n

Problem 3 Solution

\n
\n

(a) The complete code is shown below (Note we skip the NULL check for the return of malloc(), add that after each such call if required)

\n
int main() {
int **d;
int N, M, i, j;

scanf("%d %d", &N, &M);
d = (int **)malloc(N * sizeof(int *));

for(i=0; i<N; i++) {
// can also use d[i] on the left below
*(d + i) = (int *)malloc(M * sizeof(int));
}
for (i=0; i<N; i++) {
for (j=0; j<M; j++) {
scanf("%d", &d[i][j]);
}
}
}
\n

📝Notes: Freeing memory of such a 2-D integer array also needs two steps:

\n
void free_2d_array(int **array, int rows) {
for (int i = 0; i < rows; i++) {
// equivalent to free(array[i])
free(*(array+i));
}
free(array);
}
\n

(b) The solution code can be seen below

\n
unsigned int x, m = 1;
int i, count = 0;

scanf("%u", &x);

for (i=0; i<32; i++) {
if ((x & m) == 0) {
count++;
}
x = x >> 1;
}

printf("%d", count);
\n\n
\n

Bonus Problem (10 pts)

\n

Explain why printf(\"%d\", x) passes argument x by value whereas scanf(\"%d\", &x) passes the argument by reference. Can one code printf() so that it passes x by reference? If so, why is it not done?

\n

Bonus Problem Solution

\n
\n

printf() only needs a copy of the value of x to do its work of printing the value to stdout. scanf() needs the address of x so that the value entered through stdin (by default, keyboard) can be stored at the address of x. Yes, since following the address of x allows printf() to access its value. It is not necessary to reveal the address of x to printf() since it only requires its value.

\n\n
\n","categories":["Study Notes"],"tags":["C/C++ Programming"]},{"title":"Purdue CS24000 2022 Summer Final Exam Solutions","url":"/en/2024/03/24/Purdue-CS240-2022-Summer-Final/","content":"

Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solutions and study notes for the 2022 and 2023 Final exams.

\n

Introduction

\n

Below are extracted from the Summer 2023 CS24000 course homepage:

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n\n

\n\n

Summer 2022 Final Solutions and Notes

\n

Problem 1 (45 pts)

\n

(a) Which statements in the code

\n
typedef struct friend {
char *nickname;
unsigned int year;
} friend_t;

int main() {
friend t *amigo;
amigo->year = 2017;
strcpy(amigo->nickname, "fish");
}
\n

are problematic, likely to trigger segmentation fault? Augment the code by adding calls to malloc() so that the bugs are fixed.

\n

(b) Explain the difference between fun1 and fun2 which are declared as char *fun1(char *) and char (*fun2)(char *), respectively. Code a function fun3 that takes a string as argument and returns the last character of the string. You may assume that the string is of length at least 1 (not counting EOS).

\n

(c) Suppose a user enters the command, %/bin/cp file1 file2, using a shell to copy the content of file1 to file2 on one of our lab machines. From the viewpoint of the shell, from where does it read its input /bin/cp file1 file2? From the viewpoint of the app /bin/cp which is coded in C, how does it access its input which specify the names of two files whose content is to be copied? Before calling execv() what must the shell do to prepare the arguments of execv() so that /bin/cp has access to the two file names?

\n

Problem 1 Solution

\n
\n

(a) Problematic:

\n
amigo->year = 2017;
strcpy(amigo->nickname, "fish");
\n

The reason is that the pointer amigo has not been initialized to the address of any allocated memory space yet.

\n

Agumentation:

\n
amigo = (friend_t *)malloc(sizeof(friend_t));
amigo->nickname = (char *)malloc(5);
...
\n

(b) fun1 takes as argument a pointer to char and returns a pointer to char. fun2 is a function pointer to a function that takes as argument a pointer to char and returns a value of type char.

\n
char fun3(char *s) {    
while (*s != '\\0')
s++;
return *(s-1);}
\n

(c) Input /bin/cp file1 file2 is read from stdin.

\n

main(int argc, char *argv) of /bin/cp accesses the two file names via argv[1] and argv[2].

\n

Assuming a variable s is of type, char **s, a shell must allocate sufficient memory for s and copy /bin/cp into s[0]1, file1 into s[1], file2 into s[2], and set s[3] to NULL.

\n\n
\n

Problem 2 (30 pts)

\n

(a) Code a function, unsigned int countdbl(long), that takes a number of type long as input, counts the number of 0s in the bit representation of the input, and returns 0 if the count is an even number, 1 if odd. Use bit processing techniques to solve the problem. (b) gcc on our lab machine, by default, will insert code to detect stack smashing at run-time. What does gcc's code try to prevent from happening? In the case of reading input from stdin (or file), what is a common scenario and programming mistake that can lead to stack smashing? Provide an example using scanf() (or fscanf()). What issound programming practice that prevents stack smashing?

\n

Problem 2 Solution

\n
\n

(a)

\n
unsigned int countdbl(long x) {
int i;
unsigned int count = 0;
long m = 1;

for(i=0; i<64; i++) {
// Check all bits of long value from lsb to msb.
if ((x & m) == 0) count++;
x = x >> 1;
}

if ((count & 1) == 0) return 0; // Check if count is even.
else return 1; // count is odd
}
\n

(b) When a function is called by another function, gcc tries to detect if the return address has been corrupted and, if so, terminate the running program.This is to prevent the code from jumping to unintended code such as malware.A local variable of a function declared as a 1-D array overflows by input whose length is not checked when reading from stdin (or file).Example: a function contains code

\n
char buf[100];
scanf("%s", buf);
\n

which may overflow buf[] since scanf() does not check for length of the input.Sound practice: use functions to read from stdin (or file) that check for length.In the above example use fgets() instead of scanf().

\n\n
\n

Problem 3 (25 pts)

\n

Code a function that takes variable number of arguments, double multnums(char *, ...), multiplies them and returns the result as a value of type double. The fixed argument is a string that specifies how many arguments follow and their type (integer 'd' or float 'f'). For example, in the call multnums(\"dffd\", 3, 88.2, -100.5, 44), the format string \"dffd\" specifies that four arguments follow where the first character 'd' means the first argument in the variable argument list is of type integer, the second and third 'f' of type float, and the fourth 'd' of type integer. Forgo checking for errors and ignore header files. What would happen in your code if multnums is called as multnums(\"dffd\", 3, 88.2, -100.5, 44, -92, 65)? What about multnums(\"dffd\", 3, 88.2, -100.5)? Explain your reasoning.

\n

Problem 3 Solution

\n
\n
double multnums(char *a, ...) {
int x;
double y, val = 1;
va_list arglist;
\t
va_start(arglist, a);
while (*a != '\\0') {
// Check the format string, character by character until EOS.
if (*a == 'd') { // Interpret argument as int.
x = va_arg(arglist, int);
val = val * x;\t\t
}
else {
// Assumes must be 'f' since forgoing error checking.
// Interpret argument as double (not float).
y = va_arg(arglist, double);
val = val * y;
}
a++;\t
}\t
va_end(arglist);
return val;
}
\n

When a C function is defined with a variable number of arguments, it typically uses the va_arg, va_start, and va_end macros from the <stdarg.h> header to handle the variable arguments.

\n

If the input argument count does not match the format string provided to functions like printf or scanf, it can lead to undefined behavior and potentially cause crashes, memory corruption, or incorrect output/input.

\n

Here are some specific scenarios that can occur when there is a mismatch between the input arguments and the format string:

\n
    \n
  • Too few arguments:\n
      \n
    • If there are fewer arguments than the number of format specifiers in the format string, the behavior is undefined.
    • \n
    • The function may attempt to read from uninitialized memory locations or use garbage values, leading to incorrect results or crashes.
    • \n
  • \n
  • Too many arguments:\n
      \n
    • If there are more arguments than the number of format specifiers in the format string, the extra arguments will be ignored by the function.
    • \n
    • However, if the extra arguments are of a different type than expected, it can lead to incorrect interpretation of the data on the stack, potentially causing crashes or memory corruption.
    • \n
  • \n
\n\n
\n

Bonus Problem (10 pts)

\n

Suppose an ASCII file contains lines where each line is a sequence of characters ending with \\n but for the last line which ends because the end of file is reached. The goal of main() is to read and store the lines of the ASCII into a variable, char **x, where malloc() is used to allocate just enough memory to store the content of the  file. Using only basic file I/O operations discussed in class, describe in words how your code would work to accomplish this task. Be detailed in how the arguments of malloc() are determined to store the file content in x.

\n

Bonus Problem Solution

\n
\n
    \n
  1. Open file, read byte by byte until EOF is reached while counting occurrences of '' to determine the total number of lines (count plus 1). Denote this number of r.Close file.
  2. \n
  3. Use malloc() to allocate 1-D array, int *M, of size r of type int. Open file, read byte by byte, counting for each line the number of bytes. Store the line lengthin 1-D array M. Close file.
  4. \n
  5. Using 1-D array M call malloc() for each line to allocate memory to store the bytes of each line. Point x to the 1-D array of pointers to char.
  6. \n
  7. Open file. Read byte by byte the content of each line into 1-D array of pointersto char pointed to by x.
  8. \n
\n

A sample implementation (not required for this exam) is shown as below:

\n
#include <stdio.h>
#include <stdlib.h>

char **read_file(const char *filename, int *r) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
fprintf(stderr, "Error opening file: %s\\n", filename);
return NULL;
}

// 1. Count the number of lines
int c, line_count = 0;
while ((c = fgetc(file)) != EOF) {
if (c == '\\n')
line_count++;
}
line_count++; // Account for the last line without newline character
*r = line_count;

rewind(file); // Reset the file pointer to the beginning

// 2. Allocate memory for the line lengths array and store the line lengths
int *M = (int *)malloc((*r) * sizeof(int));
if (M == NULL) {
fprintf(stderr, "Error allocating memory\\n");
fclose(file);
return NULL;
}

int i = 0, length = 0;
while ((c = fgetc(file)) != EOF) {
if (c == '\\n') {
M[i++] = length;
length = 0;
} else {
length++;
}
}
M[i] = length; // Store the length of the last line

rewind(file); // Reset the file pointer to the beginning

// 3. Allocate memory for the array of character pointers
char **x = (char **)malloc((*r + 1) * sizeof(char *));
if (x == NULL) {
fprintf(stderr, "Error allocating memory\\n");
free(M);
fclose(file);
return NULL;
}

// 4. Allocate memory for each line and read the file content
for (i = 0; i < *r; i++) {
x[i] = (char *)malloc((M[i] + 1) * sizeof(char));
if (x[i] == NULL) {
fprintf(stderr, "Error allocating memory\\n");
for (int j = 0; j < i; j++)
free(x[j]);
free(x);
free(M);
fclose(file);
return NULL;
}

int j = 0;
while (j < M[i]) {
x[i][j++] = fgetc(file);
}
x[i][j] = '\\0'; // Null-terminate the line
}
x[i] = NULL; // Terminate the array of character pointers

free(M);
fclose(file);
return x;
}
\n\n
\n","categories":["Study Notes"],"tags":["C/C++ Programming"]},{"title":"Purdue MA 26500 Fall 2022 Midterm I Solutions","url":"/en/2024/01/19/Purdue-MA265-2022-Fall-Midterm1/","content":"

Linear algebra provides mathematical tools to represent and analyze data and models in higher dimensions. It is essential for machine learning, computer graphics, control theory, and other scientific and engineering fields. Starting from this post, I will provide study guides and solutions to Purdue MA26500 exams in the last few semesters.

\n

You can’t learn too much linear algebra
Benedict Gross (American mathematician, professor at the University of California San Diego and Harvard University, member of the National Academy of Sciences)

\n
\n

Introduction

\n

Purdue University is a world-renowned public research university that advances scientific, technological, engineering, and math discoveries. Purdue Department of Mathematics provides a linear algebra course MA 26500 every semester, as it is mandatory for undergraduate students of many science and engineering majors.

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

Basic Information

\n
    \n
  • Course Title: Introduction to Linear Algebra
  • \n
  • Credit Hours: 3.00
  • \n
  • Lectures: 50 minutes per session, 3 times a week, 16 weeks
  • \n
  • Course Description: A computational introduction to linear algebra, which plays a fundamental role in science, engineering, and the social sciences, and this course will provide the student a firm basis for the use of such.
  • \n
  • Key Topics: systems of linear equations; matrix algebra; vector spaces; determinants; eigenvalues and eigenvectors; diagonalization of matrices; and applications
  • \n
  • Textbook: Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald
  • \n
  • Study Guide: Study Guide for Linear Algebra and Its Applications 6th Edition by the same authors for the students.
  • \n
\n
\n

Homework and Exams

\n
    \n
  • 35 online homework assignments using MyLab Math
  • \n
  • 36 handwriting homework assignments (Spring 2024)
  • \n
  • Midterm I (Book Sections 1.1 – 3.3): 1 hour (6-week mark)
  • \n
  • Midterm II (Book Sections 4.1 – 5.7): 1 hour (12-week mark)
  • \n
  • Midterm format: a combination of multiple-choice questions and short answer questions
  • \n
  • Final (Comprehensive Common): 2 hours (16-week mark), all multiple-choice questions
  • \n
  • Grades\n
      \n
    • Online Homework - 17%
    • \n
    • Written Homework - 8%
    • \n
    • Midterm Exam I - 20%
    • \n
    • Midterm Exam II - 20%
    • \n
    • Final Exam - 35%
    • \n
  • \n
\n

Reference Links

\n\n

Fall 2022 Midterm I Solutions

\n

Problem 1 (10 points)

\n

Let \\(A=\\begin{bmatrix}1 & 2\\\\3 & 5\\\\\\end{bmatrix}\\)\\(B=\\begin{bmatrix}3 & 1\\\\4 & 1\\\\\\end{bmatrix}\\), and \\(C=AB^{-1}= \\begin{bmatrix}a & b\\\\c & d\\\\\\end{bmatrix}\\), then \\(a+b+c+d=\\)

\n
    \n
  • A. \\(-7\\)
  • \n
  • B. \\(8\\)
  • \n
  • C. \\(7\\)
  • \n
  • D. \\(-8\\)
  • \n
  • E. \\(0\\)
  • \n
\n

Problem 1 Solution

\n
\n

Because \\(C=AB^{-1}\\), we can multiple both sides by \\(B\\) and obtain \\(CB=AB^{-1}B=A\\). So \\[\n\\begin{bmatrix}a & b\\\\c & d\\\\\\end{bmatrix}\n\\begin{bmatrix}3 & 1\\\\4 & 1\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 2\\\\3 & 5\\\\\\end{bmatrix}\n\\] Further, compute at the left side \\[\n\\begin{bmatrix}3a+4b & a+b\\\\3c+4d & c+d\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 2\\\\3 & 5\\\\\\end{bmatrix}\n\\] From here we can directly see \\(a+b=2\\) and \\(c+d=5\\), so \\(a+b+c+d=7\\). The answer is C.

\n

⚠️Alert: There is no need to find the inverse of the matrix \\(B\\) and multiply the result with \\(A\\). Even if you can deduce the same answer, it is very inefficient and takes too much time.

\n\n
\n

Problem 2 (10 points)

\n

Let \\(\\mathrm L\\) be a linear transformation from \\(\\mathbb R^3\\) to \\(\\mathbb R^3\\) whose standard matrix is \\(\\begin{bmatrix}1 &2 &3\\\\0 &1 &1\\\\2 &3 & k\\\\\\end{bmatrix}\\) where \\(k\\) is a real number. Find all values of \\(k\\) such that \\(\\mathrm L\\) is one-to-one.

\n
    \n
  • A. \\(k\\neq 1\\)
  • \n
  • B. \\(k\\neq 2\\)
  • \n
  • C. \\(k\\neq 3\\)
  • \n
  • D. \\(k\\neq 4\\)
  • \n
  • E. \\(k\\neq 5\\)
  • \n
\n

Problem 2 Solution

\n
\n

For this standard matrix, do elementary row operations below to achieve row echelon form.

\n

First, add -2 times row 1 to row 3: \\[\\begin{bmatrix}1 &2 &3\\\\0 &1 &1\\\\0 &-1 &k-6\\\\\\end{bmatrix}\\] Then add row 2 to row 3: \\[\\begin{bmatrix}1 &2 &3\\\\0 &1 &1\\\\0 &0 &k-5\\\\\\end{bmatrix}\\] If \\(k=5\\), the equation \\(A\\mathbf x=\\mathbf b\\) has a free variable \\(x_3\\) and each \\(\\mathbf b\\) is the image of more than one \\(\\mathbf x\\). That is, \\(\\mathrm L\\) is not one-to-one. So the answer is E.

\n\n
\n

Problem 3 (10 points)

\n

Which of the following statements is/are always TRUE?

\n
    \n
  1. If \\(A\\) is a singular \\(8\\times 8\\) matrix, then its last column must be a linear combination of the first seven columns.

  2. \n
  3. Let \\(A\\) be a \\(5\\times 7\\) matrix such that \\(A\\cdot\\pmb x=\\pmb b\\) is consistent for any \\(\\pmb{b}∈\\mathbb{R}^5\\), and let \\(B\\) be a \\(7\\times 11\\) matrix such that \\(B\\cdot\\pmb x=\\pmb c\\) is consistent for any \\(\\pmb{c}∈\\mathbb{R}^7\\). Then, the matrix equation \\(AB\\cdot \\pmb x=\\pmb b\\) is consistent for any \\(\\pmb{b}∈\\mathbb{R}^5\\).

  4. \n
  5. For any \\(m\\times n\\) matrix \\(A\\), the dimension of the null space of \\(A\\) equals the dimension of the null space of its transpose \\(A^T\\).

  6. \n
  7. If \\(A\\) is an \\(m\\times n\\) matrix, then the set \\({A\\cdot\\pmb x|\\pmb x∈\\mathbb{R}^n}\\) is a subspace of \\(\\mathbb{R}^m\\).

  8. \n
\n
    \n
  • A. (i) only
  • \n
  • B. (i) and (ii) only
  • \n
  • C. (iv) only
  • \n
  • D. (ii) and (iv) only
  • \n
  • E. (iii) and (iv) only
  • \n
\n

Problem 3 Solution

\n
\n

For (i), a singular matrix \\(A\\) is noninvertible and has \\(det(A)=0\\). By Theorem 8 of Section 2.3, the columns of \\(A\\) form a linearly dependent set. Denote \\(A=[\\pmb{v}_1\\cdots\\pmb{v}_8]\\), then there exist weights \\(c_1, c_2,\\cdots,c_8\\), not all zero, such that \\[c_1\\pmb{v}_1+c_2\\pmb{v}_2+\\cdots+c_8\\pmb{v}_8=\\pmb{0}\\] Does this imply that statement (i) is true? No! If \\(c_8\\) is 0, \\(\\pmb{v}_8\\) is NOT a linear combination of the columns \\(\\pmb{v}_1\\) to \\(\\pmb{v}_7\\).

\n

For (ii), since \\(AB\\cdot\\pmb x=A(B\\pmb{x})=A\\pmb c=\\pmb b\\). the consistency holds for the new \\(5\\times 11\\) matrix \\(AB\\) as well. It is true.

\n

For (iii), since \\(A\\) is a \\(m\\times n\\) matrix, \\(A^T\\) is a \\(n\\times m\\) matrix. From Section 2.9 Dimension and Rank, we know that \"If a matrix \\(A\\) has \\(n\\) columns, then \\(\\mathrm rank\\,A+\\mathrm{dim\\,Nul}\\,A= n\\).\" From this, we can list \\[\\begin{align}\n\\mathrm{dim\\,Nul}\\,A&=n-rank\\,A\\\\\n\\mathrm{dim\\,Nul}\\,A^T&=m-rank\\,A^T\n\\end{align}\\] As these two dimension numbers are not necessarily the same, (iii) is not true.

\n

For (iv), we can first review the definition of subspace. From Section 2.8 Subspaces of \\(\\mathbb R^n\\),

\n
\n

A subspace of \\(\\mathbb R^n\\) is any set \\(H\\) in \\(\\mathbb R^n\\) that has three properties:
\na. The zero vector is in \\(H\\).
\nb. For each \\(\\pmb u\\) and \\(\\pmb v\\) in \\(H\\), the sum \\(\\pmb u+\\pmb v\\) is in \\(H\\).
\nc. For each \\(\\pmb u\\) in \\(H\\) and each scalar \\(c\\), the vector \\(c\\pmb u\\) is in H.

\n
\n

Denote \\(\\pmb u=A\\pmb x\\), \\(\\pmb v=A\\pmb y\\), we have \\[\\begin{align}\nA\\cdot\\pmb{0}&=\\pmb{0}\\\\\n\\pmb u+\\pmb v&=A\\pmb{x}+A\\pmb{y}=A(\\pmb{x}+\\pmb{y})\\\\\nc\\pmb u&=cA\\pmb{x}=A(c\\pmb x)\n\\end{align}\\] All the results on the right side are in the set as well. This proves that (iv) is true.

\n

As both (ii) and (iv) are true, the answer is D.

\n\n
\n

Problem 4 (10 points)

\n

Compute the determinant of the given matrix \\(\\begin{bmatrix}5 &7 &2 &2\\\\0 &3 &0 &-4\\\\-5 &-8 &0 &3\\\\0 &5 &0 &-6\\\\\\end{bmatrix}\\)

\n
    \n
  • A. \\(-20\\)
  • \n
  • B. \\(20\\)
  • \n
  • C. \\(18\\)
  • \n
  • D. \\(2\\)
  • \n
  • E. \\(0\\)
  • \n
\n

Problem 4 Solution

\n
\n

Notice that the third column of the given matrix has all entries equal to zero except \\(a_{13}\\). Taking advantage of this, we can do a cofactor expansion down the third column, then continue to do cofactor expansion with the \\(3\\times3\\) submatrix \\[\\begin{align}\n\\begin{vmatrix}5 &7 &\\color{fuchsia}2 &2\\\\0 &3 &0 &-4\\\\-5 &-8 &0 &3\\\\0 &5 &0 &-6\\\\\\end{vmatrix}&=(-1)^{1+3}\\cdot{\\color{fuchsia}2}\\cdot\\begin{vmatrix}0 &3 &-4\\\\\\color{blue}{-5} &-8 &3\\\\0 &5 &-6\\\\\\end{vmatrix}\\\\\n&=2\\cdot(-1)^{2+1}\\cdot({\\color{blue}{-5}})\\begin{vmatrix}3 &-4\\\\5 &-6\\\\\\end{vmatrix}=20\n\\end{align}\\] So the answer is B.

\n

📝Notes:This problem is directly taken from the textbook. It is the Practice Problem of Section 3.1 Introduction to Determinants.

\n\n
\n

Problem 5 (10 points)

\n

Which of the following statements is always TRUE

\n

A. If \\(A\\) is an \\(n\\times n\\) matrix with all entries being positive, then \\(\\det(A)>0\\).

\n

B. If \\(A\\) and \\(B\\) are two \\(n\\times n\\) matrices with \\(\\det(A)>0\\) and \\(\\det(B)>0\\), then also \\(\\det(A+B)>0\\).

\n

C. If \\(A\\) and \\(B\\) are two \\(n\\times n\\) matrices such that \\(AB=0\\), then both \\(A\\) and \\(B\\) are singular.

\n

D. If rows of an \\(n\\times n\\) matrix \\(A\\) are linearly independent, then \\(\\det(A^{T}A)>0\\).

\n

E. If \\(A\\) is an \\(n\\times n\\) matrix with \\(A^2=I_n\\), then \\(\\det(A)=1\\).

\n

Problem 5 Solution

\n
\n

Let's analyze the statements one by one.

\n
    \n
  • A is false. It is trivial to find a \\(2\\times 2\\) example to disprove it, such as \\[\\begin{vmatrix}1 &2\\\\3 &4\\\\\\end{vmatrix}=1\\times 4-2\\times 3=-2\\]

  • \n
  • For B, as stated in Section 3 Properties of Determinants \"\\(\\det(A+B)\\) is not equal to \\(\\det(A)+\\det(B)\\), in general\", this statement is not necessarily true. On the contrary, we can have a simple case like \\(A=\\begin{bmatrix}1 &0\\\\0 &1\\\\\\end{bmatrix}\\) and \\(B=\\begin{bmatrix}-1 &0\\\\0 &-1\\\\\\end{bmatrix}\\), then \\(\\det(A+B)=0\\).

  • \n
  • C is also false since B could be a zero matrix. If that is the case, A is not necessarily singular.

  • \n
  • For D, first with the linearly independent property, we can see \\(\\det(A)\\neq 0\\). Secondary, the multiplicative property gives \\(\\det(A^{T}A)=\\det(A^{T})\\det(A)=(\\det(A))^2\\). So it is true that \\(\\det(A^{T}A) > 0\\).

  • \n
  • For E, from \\(A^2=I_n\\), we can deduce \\(\\det(A^{2})=(\\det(A))^2=1\\), so \\(\\det(A)=\\pm 1\\). For example, if \\(A=\\begin{bmatrix}1 &0\\\\0 &-1\\\\\\end{bmatrix}\\), then \\(\\det(A)=-1\\). This statement is false.

  • \n
\n

So we conclude that the answer is D.

\n\n
\n

Problem 6 (10 points)

\n

Let \\(A=\\begin{bmatrix}1 &2 &6\\\\2 &6 &3\\\\3 &8 &10\\\\\\end{bmatrix}\\) and let its inverse \\(A^{-1}=[b_{ij}]\\). Find \\(b_{12}\\)

\n
    \n
  • A. \\(14\\)
  • \n
  • B. \\(-14\\)
  • \n
  • C. \\(1\\)
  • \n
  • D. \\(-1\\)
  • \n
  • E. \\(6\\)
  • \n
\n

Problem 6 Solution

\n
\n

According to Theorem 8 of Section 3.3, \\(A^{-1}=\\frac{\\large{1}}{\\large{\\mathrm{det}\\,A}}\\mathrm{adj}\\,A\\). Here the adjugate matrix \\(\\mathrm{adj}\\, A\\) is the transpose of the matrix of cofactors. Hence \\[b_{12}=\\frac{C_{21}}{\\mathrm{det}\\,A}\\]

\n

First computer the cofactor \\[C_{21}=(-1)^{2+1}\\begin{vmatrix}2 &6\\\\8 &10\\end{vmatrix}=(-1)\\cdot(20-48)=28\\] Now computer the determinant efficiently with row operations (Theorem 3 of Section 3.2) for \\(A\\) \\[\n{\\mathrm{det}\\,A}=\n\\begin{vmatrix}1 &2 &6\\\\2 &6 &3\\\\3 &8 &10\\\\\\end{vmatrix}=\n\\begin{vmatrix}1 &2 &6\\\\0 &2 &-9\\\\0 &2 &-8\\\\\\end{vmatrix}=\n\\begin{vmatrix}\\color{blue}1 &2 &6\\\\0 &\\color{blue}2 &-9\\\\0 &0 &\\color{blue}1\\\\\\end{vmatrix}=\\color{blue}1\\cdot\\color{blue}2\\cdot\\color{blue}1=2\n\\] So \\(C_{21}=28/2=14\\), the answer is A.

\n\n
\n

Problem 7 (10 points)

\n

Let \\(\\pmb{v_1}=\\begin{bmatrix}1\\\\2\\\\5\\\\\\end{bmatrix}\\), \\(\\pmb{v_2}=\\begin{bmatrix}-2\\\\-3\\\\1\\\\\\end{bmatrix}\\) and \\(\\pmb{x}=\\begin{bmatrix}-4\\\\-5\\\\13\\\\\\end{bmatrix}\\), and \\(\\pmb{B}=\\{\\pmb{v_1},\\pmb{v_2}\\}\\). Then \\(\\pmb B\\) is a basis for \\(H=\\mathrm{span}\\{\\mathbf{v_1,v_2}\\}\\). Determine if \\(\\pmb x\\) is in \\(H\\), and if it is, find the coordinate vector of \\(\\pmb x\\) relative to B.

\n
    \n
  • A. \\([\\pmb x]_B=\\begin{bmatrix}1\\\\2\\\\\\end{bmatrix}\\)
  • \n
  • B. \\([\\pmb x]_B=\\begin{bmatrix}2\\\\1\\\\\\end{bmatrix}\\)
  • \n
  • C. \\([\\pmb x]_B=\\begin{bmatrix}2\\\\2\\\\\\end{bmatrix}\\)
  • \n
  • D. \\([\\pmb x]_B=\\begin{bmatrix}3\\\\2\\\\\\end{bmatrix}\\)
  • \n
  • E. \\([\\pmb x]_B=\\begin{bmatrix}2\\\\3\\\\\\end{bmatrix}\\)
  • \n
\n

Problem 7 Solution

\n
\n

By definition in Section 1.3, \\(\\mathrm{Span}\\{\\pmb{v_1,v_2}\\}\\) is the collection of all vectors that can be written in the form \\(c_1\\mathbf{v_1}+c_2\\mathbf{v_2}\\) with \\(c_1,c_2\\) scalars. So asking whether a vector \\(\\pmb x\\) is in \\(\\mathrm{Span}\\{\\pmb{v_1,v_2}\\}\\) amounts to asking whether the vector equation \\[c_1\\pmb{v_1}+c_2\\pmb{v_2}=\\pmb{x}\\] has a solution. To answer this, row reduce the augmented matrix \\([\\pmb{v_1}\\,\\pmb{v_2}\\,\\pmb{x}]\\): \\[\n\\begin{bmatrix}1 &-2 &-4\\\\2 &-3 &-5\\\\5 &1 &13\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-2 &-4\\\\0 &1 &3\\\\0 &11 &33\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-2 &-4\\\\0 &1 &3\\\\0 &0 &0\\\\\\end{bmatrix}\\sim\n\\] We have a unique solution \\(c_1=2\\), \\(c_2=3\\). So the answer is E.

\n

📝Notes:This problem is similar to Example 6 of Section 1.3 Vector Equations.

\n\n
\n

Problem 8 (10 points)

\n

Let \\(T: \\mathbb R^2\\to\\mathbb R^3\\) be the linear tranformation for which \\[\nT\\left(\\begin{bmatrix}1\\\\1\\\\\\end{bmatrix}\\right)=\n\\begin{bmatrix}3\\\\2\\\\1\\\\\\end{bmatrix}\\quad \\mathrm{and}\\quad\nT\\left(\\begin{bmatrix}1\\\\2\\\\\\end{bmatrix}\\right)=\n\\begin{bmatrix}1\\\\0\\\\2\\\\\\end{bmatrix}.\n\\] (4 points)(1) Let \\(A\\) be the standard matrix of \\(T\\), find \\(A\\).

\n

(2 points)(2) Find the image of the vector \\(\\pmb u=\\begin{bmatrix}1\\\\3\\\\\\end{bmatrix}\\).

\n

(4 points)(3) Is the vector \\(\\pmb b=\\begin{bmatrix}0\\\\-2\\\\5\\\\\\end{bmatrix}\\) in the range of \\(T\\)? If so, find all the vectors \\(\\pmb x\\) in \\(\\mathbb R^2\\) such that \\(T(\\pmb x)=\\pmb b\\)

\n

Problem 8 Solution

\n
\n
    \n
  1. Referring to Theorem 10 of Section 1.9 The Matrix of a Linear Transformation, we know that \\[A=[T(\\pmb{e}_1)\\quad\\dots\\quad T(\\pmb{e}_n)]\\] So if we can find \\(T(\\pmb{e}_1)\\) and \\(T(\\pmb{e}_2)\\), we obtain \\(A\\). Remember the property \\[T(c\\pmb u+d\\pmb v)=cT(\\pmb u)+dT(\\pmb v)\\]

    \n

    We can use this property to find \\(A\\). First, it is trivial to see that \\[\\begin{align}\n \\pmb{e}_1&=\\begin{bmatrix}1\\\\0\\end{bmatrix}\n =2\\begin{bmatrix}1\\\\1\\end{bmatrix}-\\begin{bmatrix}1\\\\2\\end{bmatrix}\\\\\n \\pmb{e}_2&=\\begin{bmatrix}0\\\\1\\end{bmatrix}\n =-\\begin{bmatrix}1\\\\1\\end{bmatrix}+\\begin{bmatrix}1\\\\2\\end{bmatrix}\n \\end{align}\\] Then apply the property and compute \\[\\begin{align}\n T(\\pmb{e}_1)&=2T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)-T\\left(\\begin{bmatrix}1\\\\2\\end{bmatrix}\\right)=\\begin{bmatrix}5\\\\4\\\\0\\end{bmatrix}\\\\\n T(\\pmb{e}_2)&=-T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)+T\\left(\\begin{bmatrix}1\\\\2\\end{bmatrix}\\right)=\\begin{bmatrix}-2\\\\-2\\\\1\\end{bmatrix}\n \\end{align}\\] So \\(A\\) is \\(\\begin{bmatrix}5 &-2\\\\4 &-2\\\\0 &1\\end{bmatrix}\\).

  2. \n
  3. The image of the vector \\(\\pmb u\\) can be obtained by \\(A\\pmb u\\), the result is \\[A\\pmb u=\\begin{bmatrix}5 &-2\\\\4 &-2\\\\0 &1\\end{bmatrix}\\begin{bmatrix}1\\\\3\\\\\\end{bmatrix}=\\begin{bmatrix}-1\\\\-2\\\\3\\\\\\end{bmatrix}\\]

  4. \n
  5. This is the case of \\(A\\pmb x=\\pmb b\\) and we need to solve it. The augmented matrix here is \\[\\begin{bmatrix}5 &-2 &0\\\\4 &-2 &-2\\\\0 &1 &5\\end{bmatrix}\\] This has unique solution \\(\\begin{bmatrix}2\\\\5\\\\\\end{bmatrix}\\). So the vector \\(\\pmb b\\) is in the span of \\(T\\).

  6. \n
\n\n
\n

Problem 9 (10 points)

\n

Consider the linear system \\[\n\\begin{align}\nx + 2y +3z &= 2\\\\\ny+az &= -4\\\\\n2x+5y+a^{2}z &= a-3\n\\end{align}\n\\] (4 points)(1) Find a row echelon form for the augmented matrix of the system.

\n

(2 points)(2) For which value(s) of \\(a\\) does this system have a infinite number of solutions?

\n

(2 points)(3) For which value(s) of \\(a\\) does this system have no solution?

\n

(2 points)(4) For which value(s) of \\(a\\) does this system have a unique solution?

\n

Problem 9 Solution

\n
\n
    \n
  1. The augmented matrix and the row reduction results can be seen below \\[\n\\begin{bmatrix}1 &2 &3 &2\\\\0 &1 &a &-4\\\\2 &5 &a^2 &a-3\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &3 &2\\\\0 &1 &a &-4\\\\0 &1 &a^2-6 &a-7\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &3 &2\\\\0 &1 &a &-4\\\\0 &0 &a^2-a-6 &a-3\\\\\\end{bmatrix}\n\\] The pivots are \\(1\\), \\(1\\), and \\(a2-a-6\\).

  2. \n
  3. Look at the last row of the row echelon form, we can write it as \\((a-3)(a+2)z=(a-3)\\). Obviously if \\(a=3\\), \\(z\\) can be any number. So this system has an infinite number of solutions when \\(a=3\\).

  4. \n
  5. If \\(a=-2\\), the equation becomes \\(0\\cdot z=-5\\). This is impossible. So the system is inconsistent and has no solution when \\(a=-2\\).

  6. \n
  7. If \\(a\\neq -2\\) and \\(a\\neq 3\\)\\(z=\\frac 1 {a+2}\\), we can deduce unique solution for this system

  8. \n
\n\n
\n

Problem 10 (10 points)

\n

Let \\[\nA=\\begin{bmatrix}1 &2 &0 &-1 &2\\\\2 &3 &1 &-3 &7\\\\3 &4 &1 &-3 &9\\\\\\end{bmatrix}\n\\]

\n

(5 points)(1) Find the REDUCED row echelon form for the matrix \\(A\\).

\n

(5 points)(2) Find a basis for the null space of \\(A\\)

\n

Problem 10 Solution

\n
\n
    \n
  1. The row reduction is completed next. The symbol ~ before a matrix indicates that the matrix is row equivalent to the preceding matrix. \\[\n\\begin{bmatrix}1 &2 &0 &-1 &2\\\\2 &3 &1 &-3 &7\\\\3 &4 &1 &-3 &9\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &0 &-1 &2\\\\0 &-1 &1 &-1 &3\\\\0 &-2 &1 &0 &3\\\\\\end{bmatrix}\\sim\\begin{bmatrix}1 &2 &0 &-1 &2\\\\0 &1 &-1 &1 &-3\\\\0 &0 &1 &-2 &3\\\\\\end{bmatrix}\n\\] \\[\\sim\n\\begin{bmatrix}1 &2 &0 &-1 &2\\\\0 &1 &0 &-1 &0\\\\0 &0 &1 &-2 &3\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0 &1 &2\\\\0 &1 &0 &-1 &0\\\\0 &0 &1 &-2 &3\\\\\\end{bmatrix}\n\\]

  2. \n
  3. Referring to Section 2.8 Subspaces of \\(\\mathbb R^n\\), by definition the null space of a matrix \\(A\\) is the set Nul \\(A\\) of all solutions of the homogeneous equation \\(A\\pmb{x}=\\pmb{0}\\). Also \"A basis for a subspace \\(H\\) of \\(\\mathbb R^n\\) is a linearly independent set in \\(H\\) that spans \\(H\\)\".

    \n

    Now write the solution of \\(A\\mathrm x=\\pmb 0\\) in parametric vector form \\[[A\\;\\pmb 0]\\sim\\begin{bmatrix}1 &0 &0 &1 &2 &0\\\\0 &1 &0 &-1 &0 &0\\\\0 &0 &1 &-2 &3 &0\\\\\\end{bmatrix}\\]

    \n

    The general solution is \\(x_1=-x_4-2x_5\\), \\(x_2=x_4\\), \\(x_3=2x_4-3x_5\\), with \\(x_4\\) and \\(x_5\\) free. This can be written as \\[\n \\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n \\begin{bmatrix}-x_4-2x_5\\\\x_4\\\\2x_4-3x_5\\\\x_4\\\\x_5\\end{bmatrix}=\n x_4\\begin{bmatrix}-1\\\\1\\\\2\\\\1\\\\0\\end{bmatrix}+\n x_5\\begin{bmatrix}-2\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\n \\] So the basis for Nul \\(A\\) is \\[\n \\begin{Bmatrix}\\begin{bmatrix}-1\\\\1\\\\2\\\\1\\\\0\\end{bmatrix},\n \\begin{bmatrix}-2\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\n \\]

  4. \n
\n

📝Notes:This problem is similar to Example 6 of Section 2.8 Subspaces of \\(\\mathbb R^n\\). Read the solution for that example to get a deep understanding of this problem. Also pay attention to Example 7, Example 8, Theorem 13, and the Warning below this theorem in the same section.

\n
\n

Warning: Be careful to use pivot columns of \\(A\\) itself for the basis of Col \\(A\\). The columns of an echelon form \\(B\\) are often not in the column space of \\(A\\).

\n
\n\n
\n

Summary

\n

This test set focuses on the following points of linear algebra:

\n
    \n
  • Systems of linear equations\n
      \n
    • Elementary row operations, system consistency
    • \n
    • Row echelon form, and reduced row echelon form
    • \n
  • \n
  • Column vector, linear combinations of vectors, and span
  • \n
  • Matrix equation, solution existence, linear independence
  • \n
  • Linear transformation\n
      \n
    • Image, range, identity matrix, standard matrix
    • \n
    • Onto and one-to-one mappings
    • \n
  • \n
  • Matrix operations, the inverse of a matrix
  • \n
  • Subspace and basis, null space, dimension, and rank
  • \n
  • Determinant, Cramer's rule, adjugate matrix, and inverse formula
  • \n
\n

As can be seen, it has a very decent coverage of the basic ideas of linear algebra. So this set of exam problems provides a good test of students' knowledge of linear algebra.

\n

One thing I would like to highlight for preparing for the first exam of linear algebra is to have a complete understanding of two aspects of matrix equations. It is like two profiles of one object. As can be seen in the following snapshot taken from the textbook, a matrix equation can represent a linear combination of its column vectors. From a different viewpoint, it is used to describe the transformation that maps a vector in one space to a new vector in the other space.

\n

\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Fall 2022 Midterm II Solutions","url":"/en/2024/02/10/Purdue-MA265-2022-Fall-Midterm2/","content":"

Here comes the solution and analysis for Purdue MA 26500 Fall 2022 Midterm II. This second midterm covers topics in Chapter 4 (Vector Spaces) and Chapter 5 (Eigenvalues and Eigenvectors) of the textbook.

\n

Introduction

\n

Purdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.

\n

Textbook and Study Guide

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.

\n
\n

Exam Information

\n

MA 26500 midterm II covers the topics of Sections 4.1 – 5.7 in the textbook. It is usually scheduled at the beginning of the thirteenth week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.

\n

Based on the knowledge of linear equations and matrix algebra learned in the book chapters 1 and 2, Chapter 4 leads the student to a deep dive into the vector space framework. Chapter 5 introduces the important concepts of eigenvectors and eigenvalues. They are useful throughout pure and applied mathematics. Eigenvalues are also used to study differential equations and continuous dynamical systems, they provide critical information in engineering design,

\n

Reference Links

\n\n

Fall 2022 Midterm II Solutions

\n

Problem 1 (10 points)

\n

Let \\[A=\\begin{bmatrix}1 &0 &2 &0 &-1\\\\1 &2 &4 &-2 &-1\\\\2 &3 &7 &-3 &-2\\end{bmatrix}\\] Let \\(a\\) be the rank of \\(A\\) and \\(b\\) be the nullity of \\(A\\), find \\(5b-3a\\)

\n
    \n
  • A. 25
  • \n
  • B. 17
  • \n
  • C. 9
  • \n
  • D. 1
  • \n
  • E. 0
  • \n
\n

Problem 1 Solution

\n
\n

Do row reduction as follows:

\n
    \n
  1. Add \\(-1\\) times row 1 to row 2
  2. \n
  3. Add \\(-2\\) times row 1 to row 2
  4. \n
  5. Scale row 2 by \\(\\frac{1}{2}\\)
  6. \n
  7. Add \\(-3\\) times row 2 to row 3
  8. \n
\n

\\[\n\\begin{bmatrix}1 &0 &2 &0 &-1\\\\1 &2 &4 &-2 &-1\\\\2 &3 &7 &-3 &-2\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &0 &-1\\\\0 &2 &2 &-2 &0\\\\0 &3 &3 &-3 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}1 &0 &2 &0 &-1\\\\0 &\\color{fuchsia}1 &1 &-1 &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\n\\]

\n

So we have 2 pivots, the rank is 2 and the nullity is 3. This results in \\(5b-3a=5\\cdot 3-3\\cdot 2=9\\).

\n

The answer is C.

\n\n
\n

Problem 2 (10 points)

\n

Let \\(\\pmb u=\\begin{bmatrix}2\\\\0\\\\1\\end{bmatrix}\\), \\(\\pmb v=\\begin{bmatrix}3\\\\1\\\\0\\end{bmatrix}\\), and \\(\\pmb w=\\begin{bmatrix}1\\\\-1\\\\c\\end{bmatrix}\\) where \\(c\\) is a real number. The set \\(\\{\\pmb u, \\pmb v, \\pmb w\\}\\) is a basis for \\(\\mathbb R^3\\) provided that \\(c\\) is not equal

\n
    \n
  • A. \\(-2\\)
  • \n
  • B. \\(2\\)
  • \n
  • C. \\(-3\\)
  • \n
  • D. \\(3\\)
  • \n
  • E. \\(-1\\)
  • \n
\n

Problem 2 Solution

\n
\n

For set \\(\\{\\pmb u, \\pmb v, \\pmb w\\}\\) to be a basis for \\(\\mathbb R^3\\), the three vectors should be linearly independent. Let's create a matrix with these vectors as columns, then do row reduction like below \\[\n\\begin{bmatrix}2 &3 &1\\\\0 &1 &-1\\\\1 &0 &c\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &c\\\\0 &1 &-1\\\\2 &3 &1\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &c\\\\0 &1 &-1\\\\0 &3 &1-2c\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &c\\\\0 &1 &-1\\\\0 &0 &4-2c\\end{bmatrix}\n\\]

\n

As can be seen, we need 3 pivots to make these column vectors linearly independent. If \\(c\\) is 2, the last row above has all-zero entries, there would be only 2 pivots. So C cannot be 2 for these three vectors to be linearly independent.

\n

The answer is B.

\n\n
\n

Problem 3 (10 points)

\n

Which of the following statements is always TRUE?

\n
    \n
  • A. If \\(A\\pmb x=\\lambda\\pmb x\\) for some vector \\(\\pmb x\\), then \\(\\lambda\\) is an eigenvalue of \\(A\\).
  • \n
  • B. If \\(\\pmb v\\) is an eigenvector corresponding to eigenvalue 2, then \\(-\\pmb v\\) is an eigenvector corresonding to eigenvalue \\(-2\\).
  • \n
  • C. If \\(B\\) is invertible, then matrix \\(A\\) and \\(B^{-1}AB\\) could have different sets of eigenvalues.
  • \n
  • D. If \\(\\lambda\\) is an eigenvalue of matrix \\(A\\), then \\(\\lambda^2\\) is an eigenvalue of matrix \\(A^2\\).
  • \n
  • E. If \\(-5\\) is an eigenvalue of matrix \\(B\\), then matrix \\(B-5I\\) is not invertible.
  • \n
\n

Problem 3 Solution

\n
\n

Per definitions in 5.1 \"Eigenvectors and Eigenvalues\":

\n
\n

An eigenvector of an \\(n\\times n\\) matrix \\(A\\) is a nonzero vector \\(\\pmb x\\) such that \\(A\\pmb x=\\lambda\\pmb x\\) for some scalar \\(\\lambda\\). A scalar \\(\\lambda\\) is called an eigenvalue of \\(A\\) if there is a nontrivial solution \\(\\pmb x\\) of \\(A\\pmb x=\\lambda\\pmb x\\); such an \\(\\pmb x\\) is called an eigenvector corresponding to \\(\\lambda\\).

\n
\n

Statement A is missing the \"nonzero\" keyword, so it is NOT always TRUE.

\n

For Statement B, given \\(A\\pmb v=2\\pmb v\\), we can obtain \\(A(\\pmb{-v})=2(\\pmb{-v})\\). The eigenvalue is still 2, not \\(-2\\). This statement is FALSE.

\n

Statement C involves the definition of Similarity. Denote \\(P=B^{-1}AB\\), we have \\[BPB^{-1}=BB^{-1}ABB^{-1}=A\\] So \\(A\\) and \\(P\\) are similar. Similar matrices have the same eigenvalues (Theorem 4 in Section 5.2 \"The Characteristic Equation\"). Statement C is FALSE

\n
\n

This can be proved easily, as seen below \\[\\begin{align}\n\\det (A-\\lambda I)&=\\det (BPB^{-1}-\\lambda I)=\\det (BPB^{-1}-\\lambda BB^{-1})\\\\\n &=\\det(B)\\det(P-\\lambda I)\\det(B^{-1})\\\\\n &=\\det(B)\\det(B^{-1})\\det(P-\\lambda I)\n\\end{align}\\] Since \\(\\det(B)\\det(B^{-1})=\\det(BB^{-1})=\\det I=1\\), we see that \\(\\det (A-\\lambda I)=\\det(P-\\lambda I)\\). ■

\n
\n

For Statement D, given \\(A\\pmb x=\\lambda\\pmb x\\), we can do the following deduction \\[A^2\\pmb x=AA\\pmb x=A\\lambda\\pmb x=\\lambda A\\pmb x=\\lambda^2\\pmb x\\] So it is always TRUE that \\(\\lambda^2\\) is an eigenvalue of matrix \\(A^2\\).

\n

Statement E is FALSE. An eigenvalue \\(-5\\) means matrix \\(B-(-5)I\\) is not invertible since \\(\\det(B-(-5)I)=\\det(B+5I)=0\\). But the statement refers to a different matrix \\(B-5I\\).

\n

The answer is D.

\n\n
\n

Problem 4 (10 points)

\n

Let \\(\\mathbb P_3\\) be the vector space of all polynomials of degree at most 3. Which of the following subsets are subspaces of \\(\\mathbb P_3\\)?

\n
    \n
  1. A set of polynomials in \\(\\mathbb P_3\\) satisfying \\(p(0)=p(1)\\).
    \n
  2. \n
  3. A set of polynomials in \\(\\mathbb P_3\\) satisfying \\(p(0)p(1)=0\\).
    \n
  4. \n
  5. A set of polynomials in \\(\\mathbb P_3\\) with integer coefficients.
  6. \n
\n
    \n
  • A. (i) only
  • \n
  • B. (i) and (ii) only
  • \n
  • C. (i) and (iii) only
  • \n
  • D. (ii) only
  • \n
  • E. (ii) and (iii) only
  • \n
\n

Problem 4 Solution

\n
\n

Per the definition of Subspace in Section 4.1 \"Vector Spaces and Subspaces\"

\n
\n

A subspace of a vector space \\(V\\) is a subset \\(H\\) of \\(V\\) that has three properties:
\na. The zero vector of \\(V\\) is in \\(H\\).
\nb. \\(H\\) is closed under vector addition. That is, for each \\(\\pmb u\\) and \\(\\pmb v\\) in \\(H\\), the sum \\(\\pmb u + \\pmb v\\) is in \\(H\\).
\nc. \\(H\\) is closed under multiplication by scalars. That is, for each \\(\\pmb u\\) in \\(H\\) and each scalar \\(c\\), the vector \\(c\\pmb u\\) is in \\(H\\).

\n
\n

So to be qualified as the subspace, the subset should have all the above three properties. Denote the polynomials as \\(p(x)=a_0+a_1x+a_2x^2+a_3x^3\\).

\n
    \n
  • (i) Since \\(p(0)=p(1)\\), we have \\(a_0=a_0+a_1+a_2+a_3\\), so \\(a_1+a_2+a_3=0\\).

    \n
      \n
    • Obviously, it satisfies the first property as if \\(a_i=0\\) for all \\(i\\), \\(a_1+a_2+a_3=0\\) is true as well.
    • \n
    • Now assume \\(p_1(x)\\) and \\(p_2(x)\\) are two polynomials in this set and \\[\np_1(x)=a_0+a_1x+a_2x^2+a_3x^3\\\\\np_2(x)=b_0+b_1x+b_2x^2+b_3x^3\n\\] So we have \\(a_1+a_2+a_3=0\\) and \\(b_1+b_2+b_3=0\\). Then define a third polynomial \\[\\begin{align}\np_3(x)&=p_1(x)+p_2(x)\\\\\n&=(a_0+b_0)+(a_1+b_1)x+(a_2+b_2)x^2+(a_3+b_3)x^3\\\\\n&=c_0+c_1x+c_2x^2+c_3x^3\n\\end{align}\\] It is true that \\(c_1+c_2+c_3=0\\) as well. So the set has the second property.
    • \n
    • This set does have the third property since \\(cp(x)\\) has \\(ca_1+ca_2+ca_3=0\\) and it is also in the same set.
    • \n
    \n

    This proves that set (i) is a subspace of \\(\\mathbb P_3\\).

  • \n
  • (ii) From \\(p(0)p(1)=0\\), we can deduce that \\(a_0(a_0+a_1+a_2+a_3)=0\\). So any polynomial in this set should satisfy this condition.

    \n
      \n
    • Obviously, it satisfies the first property as if \\(a_i=0\\) for all \\(i\\), \\(a_0(a_0+a_1+a_2+a_3)=0\\) is true as well.
    • \n
    • With the same notation of \\(p_1(x)\\), \\(p_2(x)\\) and \\(p_3(x)\\). We have \\[\\begin{align}\nc_0(c_0+c_1+c_2+c_3)&=(a_0+b_0)(a_0+b_0+a_1+b_1+a_2+b_2+a_3+b_3)\\\\\n&=(a_0+b_0)((a_0+a_1+a_2+a_3)+(b_0+b_1++b_2+b_3))\\\\\n&=a_0(a_0+a_1+a_2+a_3)+a_0(b_0+b_1++b_2+b_3)+b_0(a_0+a_1+a_2+a_3)+b_0(b_0+b_1++b_2+b_3)\\\\\n&=a_0(b_0+b_1+b_2+b_3)+b_0(a_0+a_1+a_2+a_3)\n\\end{align}\\] If \\(a_0=0\\) and \\(b_0\\ne 0\\), the above ends up with \\(b_0(a_1+a_2+a_3)\\), which is not necessary equal 0. So this polynomial in this set does NOT have the second property.
    • \n
    \n

    This proves that set (ii) is NOT a subspace of \\(\\mathbb P_3\\).

  • \n
  • (iii) It is easy to tell that this set is NOT a subspace of \\(\\mathbb P_3\\). If we do multiplication by floating-point scalars, the new polynomial does not necessarily have an integer coefficient for each term and might not be in the same set.

  • \n
\n

So the answer is A.

\n\n
\n

Problem 5 (10 points)

\n

Consider the differential equation \\[\n\\begin{bmatrix}x'(t)\\\\y'(t)\\end{bmatrix}=\n\\begin{bmatrix}1 &3\\\\-2 &2\\end{bmatrix}\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}\n\\].

\n

Then the origin is

\n
    \n
  • A. an attractor
  • \n
  • B. a repeller
  • \n
  • C. a saddle point
  • \n
  • D. a spiral point
  • \n
  • E. none of the above
  • \n
\n

Problem 5 Solution

\n
\n

First, write the system as a matrix differential equation \\(\\pmb x'(t)=A\\pmb x(t)\\). We learn from Section 5.7 \"Applications to Differential Equations\" that each eigenvalue–eigenvector pair provides a solution.

\n

Now let's find out the eigenvalues of \\(A\\). From \\(\\det (A-\\lambda I)=0\\), we have \\[\\begin{vmatrix}1-\\lambda &3\\\\-2 &2-\\lambda\\end{vmatrix}=\\lambda^2-3\\lambda+8=0\\] This only gives two complex numbers as eigenvalues \\[\\lambda=\\frac{3\\pm\\sqrt{23}i}{2}\\]

\n

Referring to the Complex Eigenvalues discussion at the end of this section, \"the origin is called a spiral point of the dynamical system. The rotation is caused by the sine and cosine functions that arise from a complex eigenvalue\". Because the complex eigenvalues have a positive real part, the trajectories spiral outward.

\n

So the answer is D.

\n
\n

Refer to the following table for the mapping from \\(2\\times 2\\) matrix eigenvalues to trajectories:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
EigenvaluesTrajectories
\\(\\lambda_1>0, \\lambda_2>0\\)Repeller/Source
\\(\\lambda_1<0, \\lambda_2<0\\)Attactor/Sink
\\(\\lambda_1<0, \\lambda_2>0\\)Saddle Point
\\(\\lambda = a\\pm bi, a>0\\)Spiral (outward) Point
\\(\\lambda = a\\pm bi, a<0\\)Spiral (inward) Point
\\(\\lambda = \\pm bi\\)Ellipses (circles if \\(b=1\\))
\n
\n\n
\n

Problem 6 (10 points)

\n

Which of the following matrices are diagonalizable over the real numbers?

\n
    \n
  1. \\(\\begin{bmatrix}2 &-5\\\\3 &-6\\end{bmatrix}\\) (ii) \\(\\begin{bmatrix}4 &1\\\\0 &4\\end{bmatrix}\\) (iii) \\(\\begin{bmatrix}1 &-1 &3\\\\0 &5 &-2\\\\0 &0 &7\\end{bmatrix}\\) (iv) \\(\\begin{bmatrix}7 &1 &1\\\\0 &2 &2\\\\0 &1 &3\\end{bmatrix}\\)
  2. \n
\n
    \n
  • A. (i) and (iii) only
  • \n
  • B. (iii) and (iv) only
  • \n
  • C. (i), (iii) and (iv) only
  • \n
  • D. (i), (ii) and (iii) only
  • \n
  • E. (i), (ii) and (iv) only
  • \n
\n

Problem 6 Solution

\n
\n

This problem tests our knowledge of Theorem 6 of Section 5.3 \"Diagonalization\":

\n
\n

An \\(n\\times n\\) matrix with \\(n\\) distinct eigenvalues is diagonalizable.

\n
\n

So let's find out the eigenvalues for each matrix:

\n
    \n
    1. \n
    2. From the equation \\(\\det A-\\lambda I=0\\), we can obtain \\[\\begin{vmatrix}2-\\lambda &-5\\\\3 &-6-\\lambda\\end{vmatrix}=(\\lambda-2)(\\lambda+6)+15=(\\lambda+1)\\lambda+3)=0\\] This leads to two roots \\(\\lambda_1=-1\\), \\(\\lambda_2=-3\\).
    3. \n
  • \n
    1. \n
    2. Since this is a triangular matrix, the eigenvalue is just 4, with multiplicity 2.
    3. \n
  • \n
    1. \n
    2. For the same reason, this \\(3\\times 3\\) matrix has eigenvalues 1, 5 and 7.
    3. \n
  • \n
    1. \n
    2. Use cofactor expansion with \\(C_{1,1}\\), we have \\[\\begin{align}\n\\begin{vmatrix}7-\\lambda &1 &1\\\\0 &2-\\lambda &2\\\\0 &1 &3-\\lambda\\end{vmatrix}&=\n(7-\\lambda)(-1)^{1+1}\\begin{vmatrix}2-\\lambda &2\\\\1 &3-\\lambda\\end{vmatrix}\\\\\n&=(7-\\lambda)(\\lambda^2-5\\lambda+6-2)\\\\\n&=(7-\\lambda)(\\lambda-4)(\\lambda-1)\n\\end{align}\\] The eigenvalues are 7, 4, and 1.
    3. \n
  • \n
\n

Now we can see that (i), (iii), and (iv) have distinct eigenvalues, they are diagonalizable matrices.

\n

So the answer is C.

\n\n
\n

Problem 7 (10 points)

\n

A real \\(2\\times 2\\) matrix \\(A\\) has an eigenvalue \\(\\lambda_1=2+i\\) with corresponding eigenvector \\(\\pmb v_1=\\begin{bmatrix}3-i\\\\4+i\\end{bmatrix}\\). Which of the following is the general REAL solution to the system of differential equations \\(\\pmb x'(t)=A\\pmb x(t)\\)

\n
    \n
  • A. \\(c_{1}e^{2t}\\begin{bmatrix}3\\cos t-\\sin t\\\\4\\cos t+\\sin t\\end{bmatrix}+c_{2}e^{2t}\\begin{bmatrix}3\\sin t+\\cos t\\\\4\\sin t-\\cos t\\end{bmatrix}\\)
  • \n
  • B. \\(c_{1}e^{2t}\\begin{bmatrix}-3\\cos t+\\sin t\\\\4\\cos t-\\sin t\\end{bmatrix}+c_{2}e^{2t}\\begin{bmatrix}3\\sin t-\\cos t\\\\4\\sin t-\\cos t\\end{bmatrix}\\)
  • \n
  • C. \\(c_{1}e^{2t}\\begin{bmatrix}3\\cos t-\\sin t\\\\4\\cos t+\\sin t\\end{bmatrix}+c_{2}e^{2t}\\begin{bmatrix}3\\sin t-\\cos t\\\\4\\sin t-\\cos t\\end{bmatrix}\\)
  • \n
  • D. \\(c_{1}e^{2t}\\begin{bmatrix}3\\cos t+\\sin t\\\\4\\cos t-\\sin t\\end{bmatrix}+c_{2}e^{2t}\\begin{bmatrix}3\\sin t+\\cos t\\\\4\\sin t-\\cos t\\end{bmatrix}\\)
  • \n
  • E. \\(c_{1}e^{2t}\\begin{bmatrix}3\\cos t+\\sin t\\\\4\\cos t-\\sin t\\end{bmatrix}+c_{2}e^{2t}\\begin{bmatrix}3\\sin t-\\cos t\\\\4\\sin t+\\cos t\\end{bmatrix}\\)
  • \n
\n

Problem 7 Solution

\n
\n

From Section 5.7 \"Applications to Differential Equations\", we learn that the general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] For a real matrix, complex eigenvalues and associated eigenvectors come in conjugate pairs. Hence we know that \\(\\lambda_2=2-i\\) and \\(\\pmb{v}_2=\\begin{bmatrix}3+i\\\\4-i\\end{bmatrix}\\). However, we do not need these two to find our solution here. The real and imaginary parts of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) are (real) solutions of \\(\\pmb x'(t)=A\\pmb x(t)\\), because they are linear combinations of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) and \\(\\pmb{v}_2 e^{\\lambda_2 t}\\). (See the proof in \"Complex Eigenvalues\" of Section 5.7)

\n

Now use Euler's formula (\\(e^{ix}=\\cos x+i\\sin x\\)), we have \\[\\begin{align}\n\\pmb{v}_1 e^{\\lambda_1 t}\n&=e^{(2+i)t}\\begin{bmatrix}3-i\\\\4+i\\end{bmatrix}\\\\\n&=e^{2t}(\\cos t+i\\sin t)\\begin{bmatrix}3-i\\\\4+i\\end{bmatrix}\\\\\n&=e^{2t}\\begin{bmatrix}(3\\cos t+\\sin t)+(3\\sin t-\\cos t)i\\\\(4\\cos t-\\sin t)+(4\\sin t+\\cos t)i\\end{bmatrix}\n\\end{align}\\] The general REAL solution is the linear combination of the REAL and IMAGINARY parts of the result above, it is \\[c_1 e^{2t}\\begin{bmatrix}3\\cos t+\\sin t\\\\4\\cos t-\\sin t\\end{bmatrix}+\nc_2 e^{2t}\\begin{bmatrix}3\\sin t-\\cos t\\\\4\\sin t+\\cos t\\end{bmatrix}\\]

\n

So the answer is E.

\n\n
\n

Problem 8 (10 points)

\n

Let \\(T: M_{2\\times 2}\\to M_{2\\times 2}\\) be a linear map defined as \\(A\\mapsto A+A^T\\).

\n

(2 points) (1) Find \\(T(\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix})\\)

\n

(4 points) (2) Find a basis for the range of \\(T\\).

\n

(4 points) (3) Find a basis for the kernel of \\(T\\).

\n

Problem 8 Solution

\n
\n
    \n
  1. As the mapping rule is \\(A\\mapsto A+A^T\\), we can directly write down the transformation as below \\[T(\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix})=\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix}+\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix}^T=\\begin{bmatrix}2 &5\\\\5 &8\\end{bmatrix}\\]

  2. \n
  3. If we denote the 4 entries of a \\(2\\times 2\\) matrix as \\(\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}\\), the transformation can be written as \\[\\begin{align}\nT(\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix})\n&=\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}+\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}^T=\\begin{bmatrix}2a &b+c\\\\b+c &2d\\end{bmatrix}\\\\\n&=2a\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix}+(b+c)\\begin{bmatrix}0 &1\\\\1 &0\\end{bmatrix}+2d\\begin{bmatrix}0 &0\\\\0 &1\\end{bmatrix}\n\\end{align}\\] So the basis can be the set of three \\(3\\times 3\\) matrices like below \\[\n\\begin{Bmatrix}\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix},\\begin{bmatrix}0 &1\\\\1 &0\\end{bmatrix},\\begin{bmatrix}0 &0\\\\0 &1\\end{bmatrix}\\end{Bmatrix}\n\\]

  4. \n
  5. The kernel (or null space) of such a \\(T\\) is the set of all \\(\\pmb u\\) in vector space \\(V\\) such that \\(T(\\pmb u)=\\pmb 0\\). Write this as \\[T(\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix})=\\begin{bmatrix}2a &b+c\\\\b+c &2d\\end{bmatrix}=\\begin{bmatrix}0 &0\\\\0 &0\\end{bmatrix}\\] This leads to \\(a=d=0\\) and \\(c=-b\\). So the original matrix \\(A\\) that satified this conditioncan be represented as \\(c\\begin{bmatrix}0 &1\\\\-1 &0\\end{bmatrix}\\). This shows that \\(\\begin{bmatrix}0 &1\\\\-1 &0\\end{bmatrix}\\) (or \\(\\begin{bmatrix}0 &-1\\\\1 &0\\end{bmatrix}\\)) is the basis for the null space of \\(T\\).

  6. \n
\n\n
\n

Problem 9 (10 points)

\n

(6 points) (1) Find all the eigenvalues of matrix \\(A=\\begin{bmatrix}4 &0 &0\\\\1 &2 &1\\\\-1 &2 &3\\end{bmatrix}\\), and find a basis for the eigenspace corresponding to each of the eigenvalues.

\n

(4 points) (2) Find an invertible matrix \\(P\\) and a diagonal matrix \\(D\\) such that \\[\n\\begin{bmatrix}4 &0 &0\\\\1 &2 &1\\\\-1 &2 &3\\end{bmatrix}=PDP^{-1}\n\\]

\n

Problem 9 Solution

\n
\n
    \n
  1. Apply the equation \\(\\det A-\\lambda I=0\\), we have \\[\\begin{vmatrix}4-\\lambda &0 &0\\\\1 &2-\\lambda &1\\\\-1 &2 &3-\\lambda\\end{vmatrix}=(4-\\lambda)\\begin{vmatrix}2-\\lambda &1\\\\2 &3-\\lambda\\end{vmatrix}=-(\\lambda-4)^2(\\lambda-1)=0\\] So the eigenvalues are 4 an 1. Now to find eigenvector for each eigenvalue, we take the eigenvalue to the system \\((A-\\lambda I)\\pmb x=\\pmb 0\\) and find the basis vector(s) which would be the eigenvector.\n
      \n
    • For \\(\\lambda_1=\\lambda_2=4\\), we have the new matrix as \\[\\begin{bmatrix}0 &0 &0\\\\1 &-2 &1\\\\-1 &2 &-1\\end{bmatrix}\\sim\n \\begin{bmatrix}0 &0 &0\\\\1 &-2 &1\\\\0 &0 &0\\end{bmatrix}\\] This gives \\(x_1-2x_2+x_3=0\\) with two free variables \\(x_2\\) and \\(x_3\\). Now in parametric vector form, we can obtain \\[\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\end{bmatrix}=\\begin{bmatrix}2x_2-x_3\\\\x_2\\\\x_3\\end{bmatrix}=x_2\\begin{bmatrix}2\\\\1\\\\0\\end{bmatrix}+x_3\\begin{bmatrix}-1\\\\0\\\\1\\end{bmatrix}\\] A basis is \\(\\begin{Bmatrix}\\begin{bmatrix}2\\\\1\\\\0\\end{bmatrix},\\begin{bmatrix}-1\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\\).
    • \n
    • For \\(\\lambda_3=1\\), the new matrix is \\[\\begin{bmatrix}3 &0 &0\\\\1 &1 &1\\\\-1 &2 &2\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\0 &1 &1\\\\0 &2 &2\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\0 &1 &1\\\\0 &0 &0\\end{bmatrix}\\] This gives \\(x_1=0\\) and \\(x_2=-x_3\\) with one free variable \\(x_3\\). Again in parametric vector form, we can obtain \\[\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\end{bmatrix}=\\begin{bmatrix}0\\\\-x_3\\\\x_3\\end{bmatrix}=x_3\\begin{bmatrix}0\\\\-1\\\\1\\end{bmatrix}\\] A basis is \\(\\begin{Bmatrix}\\begin{bmatrix}0\\\\-1\\\\1\\end{bmatrix}\\end{Bmatrix}\\).
    • \n
  2. \n
  3. From the above solution we can directly write \\(P\\) and \\(D\\) below \\[P=\\begin{bmatrix}2 &-1 &0\\\\1 &0 &-1\\\\0 &1 &1\\end{bmatrix}\\quad\nD=\\begin{bmatrix}4 &0 &0\\\\0 &4 &0\\\\0 &0 &1\\end{bmatrix}\\]
  4. \n
\n\n
\n

Problem 10 (10 points)

\n

(4 points) (1) Find the eigenvalues and corresponding eigenvectors of the matrix \\[\\begin{bmatrix}-5 &1\\\\4 &-2\\end{bmatrix}\\]

\n

(2 points) (2) Find a general solution to the system of differential equations \\[\n\\begin{bmatrix}x'(t)\\\\y'(t)\\end{bmatrix}=\n\\begin{bmatrix}-5 &1\\\\4 &-2\\end{bmatrix}\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}\n\\]

\n

(4 points) (3) Let \\(\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}\\) be a particular soilution to the initial value problem \\[\n\\begin{bmatrix}x'(t)\\\\y'(t)\\end{bmatrix}=\n\\begin{bmatrix}-5 &1\\\\4 &-2\\end{bmatrix}\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix},\n\\begin{bmatrix}x(0)\\\\y(0)\\end{bmatrix}=\\begin{bmatrix}3\\\\7\\end{bmatrix}.\n\\] Find \\(x(1)+y(1)\\).

\n

Problem 10 Solution

\n
\n
    \n
  1. To find eigenvalues, write down the determinant as \\[\\begin{vmatrix}-5-\\lambda &1\\\\4 &-2-\\lambda\\end{vmatrix}=(\\lambda+6)(\\lambda+1)=0\\] So the eigenvalues are \\(\\lambda_1=-6\\) and \\(\\lambda_2=-1\\). Now follow the same method as Problem 9 solution to get eigenvectors for them.\n
      \n
    • For \\(\\lambda_1=-6\\), the new matrix is \\[\\begin{bmatrix}1 &1\\\\4 &4\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &1\\\\0 &0\\end{bmatrix}\\] The eigenvector is \\(\\begin{bmatrix}1\\\\-1\\end{bmatrix}\\).
    • \n
    • For \\(\\lambda_1=-1\\), the new matrix is \\[\\begin{bmatrix}-4 &1\\\\4 &-1\\end{bmatrix}\\sim\n \\begin{bmatrix}-4 &1\\\\0 &0\\end{bmatrix}\\] The eigenvector is \\(\\begin{bmatrix}1\\\\4\\end{bmatrix}\\).
    • \n
  2. \n
  3. The general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] So from this, since we already found out the eigenvalues and the corresponding eigenvectors, we can write down \\[\n\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}=c_1\\begin{bmatrix}1\\\\-1\\end{bmatrix}e^{-6t}+c_2\\begin{bmatrix}1\\\\4\\end{bmatrix}e^{-t}\n\\]
  4. \n
  5. Now apply the initial values of \\(x(0)\\) and \\(y(0)\\), here comes the following equations: \\[\\begin{align}\nc_1+c_2&=3\\\\\n-c_1+4c_2&=7\n\\end{align}\\] This gives \\(c_1=1\\) and \\(c_2=2\\). So \\(x(1)+y(1)=e^{-6}+2e^{-1}-e^{-6}+8e^{-1}=10e^{-1}\\).
  6. \n
\n\n
\n

Summary

\n

Here is the table listing the key knowledge points for each problem in this exam:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Problem #Points of KnowledgeBook Sections
1The Rank Theorem4.6 \"Rank\"
2Linear dependence, Invertible Matrix Theorem4.3 \"Linearly Independent Sets; Bases\", 4.6 \"Rank\"
3Eigenvectors and Eigenvalues5.1 \"Eigenvectors and Eigenvalues\"
4Vector Spaces and Subspaces4.1 \"Vector Spaces and Subspaces\"
5Eigenfunctions of the Differential Equation5.7 \"Applications to Differential Equations\"
6The Diagonalization Theorem, Diagonalizing Matrices5.3 \"Diagonalization\"
7Complex Eigenvalues and Eigenvectors5.5 \"Complex Eigenvalues\"
8Kernel and Range of a Linear Transformation4.2 \"Null Spaces, Column Spaces, and Linear Transformations\"
9Eigenvalues, Basis for Eigenspace, Diagonalizing Matrices5.1 \"Eigenvectors and Eigenvalues\", 5.3 \"Diagonalization\"
10Eigenvectors and Eigenvalues5.1 \"Eigenvectors and Eigenvalues\", 5.7 \"Applications to Differential Equations\"
\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Spring 2022 Final Exam Solutions","url":"/en/2024/04/18/Purdue-MA265-2022-Spring-Final/","content":"

Here comes the solution and analysis for Purdue MA 26500 Spring 2022 Final exam. This exam covers all topics from Chapter 1 (Linear Equations in Linear Algebra) to Chapter 7 Section 1 (Diagonalization of Symmetric Matrices).

\n

Introduction

\n

Purdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.

\n

Textbook and Study Guide

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.

\n
\n

Exam Information

\n

MA 26500 Final exam covers all the topics from Chapter 1 to Chapter 7 Sections 1 in the textbook. This is a two-hour comprehensive common final exam given during the final exam week. There are 25 multiple-choice questions on the final exam.

\n

Reference Links

\n\n

Spring 2022 Final Exam Solutions

\n

Problem 1

\n

\n

Problem 1 Solution

\n
\n

Start with the augmented matrix of the system, do row reduction like below

\n

\\[\n\\left[\\begin{array}{ccc|c}1&2&3&16\\\\2&0&-2&14\\\\3&2&1&3a\\end{array}\\right]\\sim\n\\left[\\begin{array}{ccc|c}1&2&3&16\\\\0&-4&-8&-18\\\\0&-4&-8&3a-48\\end{array}\\right]\\sim\n\\left[\\begin{array}{ccc|c}1&2&3&16\\\\0&-4&-8&-18\\\\0&0&0&3a-30\\end{array}\\right]\n\\]

\n

Clearly, this system of equations is consistent when \\(a=10\\). So the answer is B.

\n\n
\n

Problem 2

\n

\n

Problem 2 Solution

\n
\n

According to the properties of determinants:

\n
\n

Let A be a square matrix.
\na. If a multiple of one row of \\(A\\) is added to another row to produce a matrix \\(B\\),then \\(\\det B =\\det A\\).
\nb. If two rows of \\(A\\) are interchanged to produce \\(B\\), then \\(\\det B=-\\det A\\).
\nc. If one row of A is multiplied by \\(k\\) to produce B, then \\(\\det B=k\\cdot\\det A\\).

\n
\n

Also since \\(\\det A^T=\\det A\\), a row operation on \\(A^T\\) amounts to a column operation on \\(A\\). The above property is true for column operations as well.

\n

With these properties in mind, we can do the following

\n

\\[\\begin{align}\n\\begin{vmatrix}d&2a&g+d\\\\e&2b&h+e\\\\f&2c&i+f\\end{vmatrix}\n&=2\\times \\begin{vmatrix}d&a&g+d\\\\e&b&h+e\\\\f&c&i+f\\end{vmatrix}=\n 2\\times \\begin{vmatrix}d&a&g\\\\e&b&h\\\\f&c&i\\end{vmatrix}=\n 2\\times (-1)\\times \\begin{vmatrix}a&d&g\\\\b&e&h\\\\c&f&i\\end{vmatrix}\\\\\n&=(-2)\\times \\begin{vmatrix}a&b&c\\\\d&e&f\\\\g&h&i\\end{vmatrix}=(-2)\\times 1=-2\n\\end{align}\\]

\n

So the answer is A.

\n\n
\n

Problem 3

\n

\n

Problem 3 Solution

\n
\n

Denote \\(A=BCB^{-1}\\), it can be seen that \\[\\det A=\\det BCB^{-1}=\\det B\\det C\\det B^{-1}=\\det (BB^{-1})\\det C=\\det C\\]

\n

Thus we can directly write down the determinant calculation process like below (applying row operations) \\[\n\\begin{vmatrix}1&2&3\\\\1&4&5\\\\-1&3&7\\end{vmatrix}=\n\\begin{vmatrix}1&2&3\\\\0&2&2\\\\0&5&10\\end{vmatrix}=\n1\\times (-1)^{1+1}\\begin{vmatrix}2&2\\\\5&10\\end{vmatrix}=\n1\\times (2\\times 10-2\\times 5)=10\n\\]

\n

So the answer is B.

\n\n
\n

Problem 4

\n

\n

Problem 4 Solution

\n
\n\n\n
\n

Problem 5

\n

\n

Problem 5 Solution

\n
\n\n\n
\n

Problem 6

\n

\n

Problem 6 Solution

\n
\n

Note the trace of a square matrix \\(A\\) is the sum of the diagonal entries in A and is denoted by tr \\(A\\).

\n

Remember the formula for inverse matrix \\[\nA^{-1}=\\frac{1}{\\det A}\\text{adj}\\;A=[b_{ij}]\\\\\nb_{ij}=\\frac{C_{ji}}{\\det A}\\qquad C_{ji}=(-1)^{i+j}\\det A_{ji}\n\\] Where \\(\\text{adj}\\;A\\) is the adjugate of \\(A\\), \\(C_{ji}\\) is a cofactor of \\(A\\), and \\(A_{ji}\\) denotes the submatrix of \\(A\\) formed by deleting row \\(j\\) and column \\(i\\).

\n

Now we can find the answer step-by-step:

\n
    \n
  1. Calculate the determinant of \\(A\\) \\[\n\\begin{vmatrix}1&2&7\\\\1&3&12\\\\2&5&20\\end{vmatrix}=\n\\begin{vmatrix}1&2&7\\\\0&1&5\\\\0&1&6\\end{vmatrix}=\n\\begin{vmatrix}1&2&7\\\\0&1&5\\\\0&0&1\\end{vmatrix}=1\n\\]

  2. \n
  3. Calculate \\(b_{11}\\), \\(b_{22}\\), and \\(b_{33}\\) \\[\nb_{11}=\\frac{C_{11}}{1}=\\begin{vmatrix}3&12\\\\5&20\\end{vmatrix}=0\\\\\nb_{22}=\\frac{C_{22}}{1}=\\begin{vmatrix}1&7\\\\2&20\\end{vmatrix}=6\\\\\nb_{33}=\\frac{C_{33}}{1}=\\begin{vmatrix}1&2\\\\1&3\\end{vmatrix}=1\n\\]

  4. \n
  5. Get the trace of \\(A^{-1}\\) \\[\\text{tr}\\;A^{-1}=b_{11}+b_{22}+b_{33}=0+6+1=7\\]

  6. \n
\n

So the answer is C.

\n\n
\n

Problem 7

\n

\n

Problem 7 Solution

\n
\n\n\n
\n

Problem 8

\n

\n

Problem 8 Solution

\n
\n\n\n
\n

Problem 9

\n

\n

Problem 9 Solution

\n
\n\n\n
\n

Problem 10

\n

\n

Problem 10 Solution

\n
\n\n\n
\n

Problem 11

\n

\n

Problem 11 Solution

\n
\n\n\n
\n

Problem 12

\n

\n

Problem 12 Solution

\n
\n\n\n
\n

Problem 13

\n

\n

Problem 13 Solution

\n
\n\n\n
\n

Problem 14

\n

\n

Problem 14 Solution

\n
\n\n\n
\n

Problem 15

\n

\n

Problem 15 Solution

\n
\n\n\n
\n

Problem 16

\n

\n

Problem 16 Solution

\n
\n\n\n
\n

Problem 17

\n

\n

Problem 17 Solution

\n
\n\n\n
\n

Problem 18

\n

\n

Problem 18 Solution

\n
\n\n\n
\n

Problem 19

\n

\n

Problem 19 Solution

\n
\n\n\n
\n

Problem 20

\n

\n

Problem 20 Solution

\n
\n\n\n
\n

Problem 21

\n

\n

Problem 21 Solution

\n
\n\n\n
\n

Problem 22

\n

\n

Problem 22 Solution

\n
\n\n\n
\n

Problem 23

\n

\n

Problem 23 Solution

\n
\n\n\n
\n

Problem 24

\n

\n

Problem 24 Solution

\n
\n\n\n
\n

Problem 25

\n

\n

Problem 25 Solution

\n
\n\n\n
\n

Other MA265 Final Exam Solutions

\n\n

\nMA 265 Fall 2022 Final\n

\n\n

\nMA 265 Sprint 2023 Final\n

\n\n

\nMA 265 Fall 2019 Final\n

\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Spring 2022 Midterm II Solutions","url":"/en/2024/02/29/Purdue-MA265-2022-Spring-Midterm2/","content":"

Here comes the solution and analysis for Purdue MA 26500 Spring 2022 Midterm II. This second midterm covers topics in Chapter 4 (Vector Spaces) and Chapter 5 (Eigenvalues and Eigenvectors) of the textbook.

\n

Introduction

\n

Purdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.

\n

Textbook and Study Guide

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.

\n
\n

Exam Information

\n

MA 26500 midterm II covers the topics of Sections 4.1 – 5.7 in the textbook. It is usually scheduled at the beginning of the thirteenth week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.

\n

Based on the knowledge of linear equations and matrix algebra learned in the book chapters 1 and 2, Chapter 4 leads the student to a deep dive into the vector space framework. Chapter 5 introduces the important concepts of eigenvectors and eigenvalues. They are useful throughout pure and applied mathematics. Eigenvalues are also used to study differential equations and continuous dynamical systems, they provide critical information in engineering design,

\n

Reference Links

\n\n

Spring 2022 Midterm II Solutions

\n

Problem 1 (10 points)

\n

\n

Problem 1 Solution

\n
\n

A From the following \\[c_1(\\pmb u+\\pmb v)+c_2(\\pmb v+\\pmb w)+c_3\\pmb w=c_1\\pmb u+(c_1+c_2)\\pmb v+(c_2+c_3)\\pmb w\\] it can be concluded that if \\(\\pmb u\\), \\(\\pmb v\\), and \\(\\pmb w\\) are linearly independent, it is always true that \\(\\pmb u+\\pmb v\\), \\(\\pmb v+\\pmb w\\), and \\(\\pmb w\\) are linearly independent. So this statement is always true.

\n

B This is also true. If the number of vectors is greater than the number of entries (\\(n\\) here), the transformation matrix has more columns than rows. The column vectors are not linearly independent.

\n

C This is always true per the definition of basis and spanning set.

\n

D If the nullity of a \\(m\\times n\\) matrix \\(A\\) is zero, \\(rank A=n\\). This means there the column vectors form a linearly independent set, and there is one pivot in each column. However, this does not mean \\(A\\pmb x=\\pmb b\\) has a unique solution for every \\(\\pmb b\\). For example, see the following augmented matrix in row echelon form (after row reduction): \\[\n\\begin{bmatrix}1 &\\ast &\\ast &b_1\\\\0 &1 &\\ast &b_2\\\\0 &0 &1 &b_3\\\\0 &0 &0 &b_4\\end{bmatrix}\n\\] If \\(b_4\\) is not zero, the system is inconsistent and there is no solution. So this one is NOT always true.

\n

E This is always true since the rank of a \\(m\\times n\\) matirx is always in the range of \\([0, n]\\).

\n

So the answer is D.

\n\n
\n

Problem 2 (10 points)

\n

\n

Problem 2 Solution

\n
\n

Denote \\(3\\times 3\\) matrix as \\(A=\\begin{bmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\end{bmatrix}\\), then from the given condition we can get \\[\\begin{align}\n&\\begin{bmatrix}1 &0 &0\\\\0 &2 &0\\\\0 &0 &3\\end{bmatrix}\\begin{bmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\end{bmatrix}=\\begin{bmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\end{bmatrix}\\begin{bmatrix}1 &0 &0\\\\0 &2 &0\\\\0 &0 &3\\end{bmatrix}\\\\\n\\implies&\\begin{bmatrix}a &b &c\\\\2d &2e &2f\\\\3g &3h &3i\\end{bmatrix}=\\begin{bmatrix}a &2b &3c\\\\d &2e &3f\\\\g &2h &3i\\end{bmatrix}\\\\\n\\implies&A=\\begin{bmatrix}a &0 &0\\\\0 &2e &0\\\\0 &0 &3i\\end{bmatrix}=a\\begin{bmatrix}1 &0 &0\\\\0 &0 &0\\\\0 &0 &0\\end{bmatrix}+\n2e\\begin{bmatrix}0 &0 &0\\\\0 &1 &0\\\\0 &0 &0\\end{bmatrix}+\n3i\\begin{bmatrix}0 &0 &0\\\\0 &0 &0\\\\0 &0 &1\\end{bmatrix}\n\\end{align}\\]

\n

It can be seen that there are three basis vectors for this subspace and the dimension is 3. The answer is A.

\n

Notice the effects of left-multiplication and right-multiplication of a diagonal matrix.

\n\n
\n

Problem 3 (10 points)

\n

\n

Problem 3 Solution

\n
\n

From \\(\\det A-\\lambda I\\), it becomes \\[\\begin{align}\n\\begin{vmatrix}4-\\lambda &0 &0 &0\\\\-2 &-1-\\lambda &0 &0\\\\10 &-9 &6-\\lambda &a\\\\1 &5 &a &3-\\lambda\\end{vmatrix}\n&=(4-\\lambda)(-1-\\lambda)((6-\\lambda)(3-\\lambda)-a^2)\\\\\n&=(\\lambda-4)(\\lambda+1)(\\lambda^2-9\\lambda+18-a^2)\n\\end{align}\\]

\n

So if 2 is an eigenvalue for the above, the last multiplication item becomes \\((2^2-18+18-a^2)\\) that should be zero. So \\(a=\\pm 2\\).

\n

The answer is E.

\n\n
\n

Problem 4 (10 points)

\n

\n

Problem 4 Solution

\n
\n

(i) Referring to Theorem 4 in Section 5.2 \"The Characteristic Equation\" >If \\(n\\times n\\) matrices \\(A\\) and \\(B\\) are similar, then they have the same characteristic polynomial and hence the same eigenvalues (with the same multiplicities).

\n

So this statement must be TRUE.

\n

(ii) If the columns of \\(A\\) are linearly independent, \\(A\\pmb x=\\pmb 0\\) only has trivial solution and \\(A\\) is an invertible matrix. This also means \\(\\det A\\neq 0\\). From here, it must be TRUE that \\(\\det A-0 I\\neq 0\\). So 0 is NOT an eigenvalue of \\(A\\). This statement is FALSE.

\n

(iii) A matrix \\(A\\) is said to be diagonalizable if it is similar to a diagonal matrix, which means that there exists an invertible matrix \\(P\\) such that \\(P^{-1}AP\\) is a diagonal matrix. In other words, \\(A\\) is diagonalizable if it has a linearly independent set of eigenvectors that can form a basis for the vector space.

\n

However, the condition for diagonalizability does not require that all eigenvalues be nonzero. A matrix can be diagonalizable even if it has one or more zero eigenvalues. For example, consider the following matrix: \\[A=\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix}\n=\\begin{bmatrix}1 &0\\\\0 &1\\end{bmatrix}\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix}\\begin{bmatrix}1 &0\\\\0 &1\\end{bmatrix}\\] This matrix has one nonzero eigenvalue (\\(λ = 1\\)) and one zero eigenvalue (\\(λ = 0\\)). However, it is diagonalizable with the identity matrix as \\(P\\) and \\(D=A\\).

\n

So this statement is FALSE.

\n

(iv) Similar matrices have the same eigenvalues (with the same multiplicities). Hence \\(-\\lambda\\) is also an eigenvalue of \\(B\\). Then we have \\(B\\pmb x=-\\lambda\\pmb x\\). From this, \\[\nBB\\pmb x=B(-\\lambda)\\pmb x=(-\\lambda)B\\pmb x=(-\\lambda)(-\\lambda)\\pmb x=\\lambda^2\\pmb x\n\\] So \\(\\lambda^2\\) is an eigenvalue of \\(B^2\\). Following the same deduction, we can prove that \\(\\lambda^4\\) is an eigenvalue of \\(B^4\\). This statement is TRUE.

\n

(v) Denote \\(A=PBP^{-1}\\). If \\(A\\) is diagonizible, then \\(A=QDQ^{-1}\\) for some diagonal matrix \\(D\\). Now we can also write down \\[B=P^{-1}AP=P^{-1}QDQ^{-1}P=(P^{-1}Q)D(P^{-1}Q)^{-1}\\] This proves that \\(B\\) is also diagonalizable. This statement is TRUE.

\n

Since statements (ii) and (iii) are FALSE and the rest are TRUE, the answer is D.

\n\n
\n

Problem 5 (10 points)

\n

\n

Problem 5 Solution

\n
\n

(i) Obviously \\(x=y=z=0\\) does not satisfy \\(x+2y+3z=1\\), this subset is NOT a subspace of \\(\\mathbb R^3\\).

\n

(ii) This subset is a subspace of \\(\\mathbb R^3\\) since it has all the three properties of subspace:

\n
    \n
  1. Be \\(x=y=z=0\\) satisfies \\(10x-2y=z\\), so the set includes the zero vector.
  2. \n
  3. Because \\(10(x_1+x_2)-2(y_1+y_2)=z_1+z_2\\), it is closed under vector addition.
  4. \n
  5. \\(10cx-2cy=cz\\), it is closed under scalar multiplication as well.
  6. \n
\n

(iii) Here \\(p(t)=a_0+a_1t+a_2t^2+a_3t^3\\) and \\(a_3\\neq 0\\). This set does not include zero polynomial. Besides, if \\(p_1(t)=t^3+t\\) and \\(p_2(t)=-t^3+t\\), then \\(p_1(t)+p_2(t)=2t\\). This result is not a polynomial of degree 3. So this subset is NOT closed under vector addition and is NOT a subspace of \\(\\mathbb P_3\\).

\n

(iv) The condition \\(p(2)=0\\) means \\(a_0+2a_1+4a_3+8a_3=0\\). It does include zero polynomial. It also satisfies the other two properties because \\[\\begin{align}\ncp(2)&=c(a_0+2a_1+4a_3+8a_3)=0\\\\\np_1(2)+p_2(2)&=(a_0+2a_1+4a_3+8a_3)+(b_0+2b_1+4b_3+8b_3)=0\n\\end{align}\\] So this set is indeed a subset of \\(\\mathbb P_3\\).

\n

Since we have (ii) and (iv) be our choices, the answer is A.

\n\n
\n

Problem 6 (10 points)

\n

\n

Problem 6 Solution

\n
\n

\\[\n\\begin{vmatrix}4-\\lambda &2\\\\3 &5-\\lambda\\end{vmatrix}=\\lambda^2-9\\lambda+20-6=(\\lambda-2)(\\lambda-7)\n\\]

\n

So there are two eigenvalues 2 and 7. Since both are positive, the origin is a repeller. The answer is B.

\n\n
\n

Problem 7 (10 points)

\n

\n

Problem 7 Solution

\n
\n

From Section 5.7 \"Applications to Differential Equations\", we learn that the general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] For a real matrix, complex eigenvalues and associated eigenvectors come in conjugate pairs. The real and imaginary parts of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) are (real) solutions of \\(\\pmb x'(t)=A\\pmb x(t)\\), because they are linear combinations of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) and \\(\\pmb{v}_2 e^{\\lambda_2 t}\\). (See the proof in \"Complex Eigenvalues\" of Section 5.7)

\n

Now use Euler's formula (\\(e^{ix}=\\cos x+i\\sin x\\)), we have \\[\\begin{align}\n\\pmb{v}_1 e^{\\lambda_1 t}\n&=e^{1+i}\\begin{bmatrix}1-2i\\\\3+4i\\end{bmatrix}\\\\\n&=e^t(\\cos t+i\\sin t)\\begin{bmatrix}1-2i\\\\3+4i\\end{bmatrix}\\\\\n&=e^t\\begin{bmatrix}\\cos t+2\\sin t+i(\\sin t-2\\cos t)\\\\3\\cos t-4\\sin t+i(3\\sin t+4\\cos t)\\end{bmatrix}\n\\end{align}\\] The general REAL solution is the linear combination of the REAL and IMAGINARY parts of the result above, it is \\[c_1 e^t\\begin{bmatrix}\\cos t+2\\sin t\\\\3\\cos t-4\\sin t\\end{bmatrix}+\nc_2 e^t\\begin{bmatrix}\\sin t-2\\cos t\\\\3\\sin t+4\\cos t\\end{bmatrix}\\]

\n

The answer is A.

\n\n
\n

Problem 8 (10 points)

\n

\n

Problem 8 Solution

\n
\n

(1) Since \\(p(t)=at^2+bt+c\\), its derivative is \\(p'(t)=2at+b\\). So we can have \\[\nT(at^2+bt+c)=\\begin{bmatrix}c &b\\\\a+b+c &2a+b\\end{bmatrix}\n\\]

\n

(2) From the result of (1) above, we can directly write down that \\(c=1\\) and \\(b=2\\). Then because \\(2a+b=4\\), \\(a=2\\). So \\(p(t)=t^2+2t+1\\).

\n

(3) Write down this transformation as the parametric vector form like below \\[\n\\begin{bmatrix}c &b\\\\a+b+c &2a+b\\end{bmatrix}=\na\\begin{bmatrix}0 &0\\\\1 &2\\end{bmatrix}+\nb\\begin{bmatrix}0 &1\\\\1 &1\\end{bmatrix}+\nc\\begin{bmatrix}1 &0\\\\1 &0\\end{bmatrix}\n\\] So a basis for the range of \\(T\\) is \\[\n\\begin{Bmatrix}\n\\begin{bmatrix}0 &0\\\\1 &2\\end{bmatrix},\n\\begin{bmatrix}0 &1\\\\1 &1\\end{bmatrix},\n\\begin{bmatrix}1 &0\\\\1 &0\\end{bmatrix}\n\\end{Bmatrix}\n\\]

\n\n
\n

Problem 9 (10 points)

\n

\n

Problem 9 Solution

\n
\n

(1) First find all the eigenvalues using \\(\\det A-\\lambda I=0\\) \\[\n\\begin{align}\n\\begin{vmatrix}2-\\lambda &0 &0\\\\1 &5-\\lambda &1\\\\-1 &-3 &1-\\lambda\\end{vmatrix}&=(2-\\lambda)\\begin{vmatrix}5-\\lambda &1\\\\-3 &1\\lambda\\end{vmatrix}\\\\\n&=(2-\\lambda)(\\lambda^2-6\\lambda+5+3)\\\\\n&=(2-\\lambda)(\\lambda-2)(\\lambda-4)\n\\end{align}\n\\] So there are two eigenvalues 2 with multiplicity and 4.

\n

Now find out the eigenvector(s) for each eigenvalue

\n
    \n
  • For \\(\\lambda_1=\\lambda_2=2\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}0 &0 &0\\\\1 &3 &1\\\\-1 &-3 &-1\\end{bmatrix}\\sim\n\\begin{bmatrix}0 &0 &0\\\\1 &3 &1\\\\0 &0 &0\\end{bmatrix}\n\\] Convert this result to a parametric vector form with two free variables \\(x_2\\) and \\(x_3\\) \\[\n\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\end{bmatrix}=\n\\begin{bmatrix}-3x_2-x_3\\\\x_2\\\\x_3\\end{bmatrix}=\nx_2\\begin{bmatrix}-3\\\\1\\\\0\\end{bmatrix}+x_3\\begin{bmatrix}-1\\\\0\\\\1\\end{bmatrix}\n\\] So the basis for the eigenspace is \\(\\begin{Bmatrix}\\begin{bmatrix}-3\\\\1\\\\0\\end{bmatrix},\\begin{bmatrix}-1\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\\).

  • \n
  • For \\(\\lambda_3=4\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}-2 &0 &0\\\\1 &1 &1\\\\-1 &-3 &-3\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0\\\\0 &1 &1\\\\0 &-2 &-2\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0\\\\0 &1 &1\\\\0 &0 &0\\end{bmatrix}\n\\] This ends up with \\(x_1=0\\) and \\(x_2=-x_3\\). So the eigenvector is \\(\\begin{bmatrix}0\\\\-1\\\\1\\end{bmatrix}\\) or \\(\\begin{bmatrix}0\\\\1\\\\-1\\end{bmatrix}\\). The basis for the corresponding eigenspace is \\(\\begin{Bmatrix}\\begin{bmatrix}0\\\\-1\\\\1\\end{bmatrix}\\end{Bmatrix}\\) or \\(\\begin{Bmatrix}\\begin{bmatrix}0\\\\1\\\\-1\\end{bmatrix}\\end{Bmatrix}\\).

  • \n
\n

(2) From the answers of (1), we can directly write down \\(P\\) and \\(D\\) as \\[\nP=\\begin{bmatrix}-3 &-1 &0\\\\1 &0 &-1\\\\0 &1 &1\\end{bmatrix},\\;\nD=\\begin{bmatrix}2 &0 &0\\\\0 &2 &0\\\\0 &0 &4\\end{bmatrix}\n\\]

\n\n
\n

Problem 10 (10 points)

\n

\n

Problem 10 Solution

\n
\n

(1) First find the eigenvalues using \\(\\det A-\\lambda I=0\\) \\[\n\\begin{align}\n\\begin{vmatrix}9-\\lambda &5\\\\-6 &-2-\\lambda\\end{vmatrix}\n&=\\lambda^2-7\\lambda-18-(-5)\\cdot 6\\\\\n&=\\lambda^2-7\\lambda+12\\\\\n&=(\\lambda-3)(\\lambda-4)\n\\end{align}\n\\] So there are two eigenvalues 3 and 4.

\n
    \n
  • For \\(\\lambda_1=3\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}6 &5\\\\-6 &5\\end{bmatrix}\\sim\n\\begin{bmatrix}6 &5\\\\0 &0\\end{bmatrix}\n\\] So the eigenvector can be \\(\\begin{bmatrix}-5\\\\6\\end{bmatrix}\\).

  • \n
  • Likewise, for \\(\\lambda_2=4\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}5 &5\\\\-6 &-6\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &1\\\\0 &0\\end{bmatrix}\n\\] So the eigenvector can be \\(\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\).

  • \n
\n

(2) With the eigenvalues and corresponding eigenvectors known, we can apply them to the general solution formula \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] So the answer is \\[\n\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}=c_1\\begin{bmatrix}-5\\\\6\\end{bmatrix}e^{3t}+c_2\\begin{bmatrix}-1\\\\1\\end{bmatrix}e^{4t}\n\\]

\n

(3) Apply the initial values of \\(x(0)\\) and \\(y(0)\\), here comes the following equations: \\[\\begin{align}\n-5c_1-c_2&=1\\\\\n6c_1+c_2&=0\n\\end{align}\\] This gives \\(c_1=1\\) and \\(c_2=-6\\). So \\(x(1)+y(1)=-5e^{3}+6e^4+6e^3-6e^4=e^3\\).

\n\n
\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Fall 2023 Midterm I Solutions","url":"/en/2024/01/28/Purdue-MA265-2023-Fall-Midterm1/","content":"

This is the 3rd study notes post for the college linear algebra course. Here is the review of Purdue MA 26500 Fall 2023 midterm I. I provide solutions to all exam questions as well as concise explanations.

\n

There is hardly any theory which is more elementary [than linear algebra], in spite of the fact that generations of professors and textbook writers have obscured its simplicity by preposterous calculations with matrices.
Jean Dieudonné (1906~1992, French mathematician, notable for research in abstract algebra, algebraic geometry, and functional analysis.)

\n
\n

Introduction

\n

Purdue University Department of Mathematics provides an introductory-level linear algebra course MA 26500 every semester. Undergraduate students of science and engineering majors taking this course would gain a good mathematical foundation for their advanced studies in machine learning, computer graphics, control theory, etc.

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.

\n
\n

MA 26500 midterm I covers the topics in Sections 1.1 – 3.3 of the textbook. It is usually scheduled at the beginning of the seventh week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.

\n

Here are a few extra reference links for Purdue MA 26500:

\n\n

Fall 2023 Midterm I Solutions

\n

Problem 1 (10 points)

\n

\n

Problem 1 Solution

\n
\n

Because \\(C=B^{-1}A\\), we can left-multiply both sides by \\(B\\) and obtain \\(BC=BB^{-1}A=A\\). So \\[\n\\begin{bmatrix}0 & 1\\\\1 & 5\\\\\\end{bmatrix}\n\\begin{bmatrix}a & b\\\\c & d\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 1\\\\3 & 2\\\\\\end{bmatrix}\n\\] Further, compute matrix multiplication at the left side \\[\n\\begin{bmatrix}c &d\\\\a+5c &b+5d\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 1\\\\3 & 2\\\\\\end{bmatrix}\n\\] From here we can directly get \\(c=d=1\\), then \\(a=-2\\) and \\(b=-3\\). This leads to \\(a+b+c+d=-3\\).

\n

The answer is A.

\n\n
\n

Problem 2 (10 points)

\n

\n

Problem 2 Solution

\n
\n

The reduced row echelon form has the same number of pivots as the original matrix. And the rank of a matrix \\(A\\) is just the number of pivot columns in \\(A\\). From these, we can deduce statement (iii) is true.

\n

Per the Rank Theorem (rank \\(A\\) + dim Nul \\(A\\) = \\(n\\)), since \\(\\mathrm{Rank}(A)=\\mathrm{Rank}(R)\\), we obtain \\(\\mathrm{Nul}(A)=\\mathrm{Nul}(R)\\). So statement (i) is true as well.

\n

For a square matrix \\(A\\), suppose that transforming \\(A\\) to a matrix in reduced row-echelon form using elementary row operations \\(E_kE_{k−1}⋯E_1A=R\\). Taking the determinants of both sides, we get \\(\\det E_kE_{k−1}⋯E_1A=\\det R\\). Now, using the fact that the determinant of a product of matrices is the same as the product of the determinants of the matrices, we get that \\[\\det A=\\frac{\\det R}{\\det E_1⋯\\det E_k}\\]

\n

According to the description in the \"Proofs of Theorems 3 and 6\" part in Section 3.2 Properties of Determinants, it is proven that \\(\\det E\\) would be either 1, -1, or a scalar. Taking all these into consideration, if \\(\\det R\\) is zero, \\(\\det A\\) must be zero. Statement (v) is true.

\n

📝Notes:The reduced row echelon form of a square matrix is either the identity matrix or contains a row of 0's. Hence, \\(\\det R\\) is either 1 or 0.

\n

Now look back at statement (ii), the column space of the matrix \\(A\\) is not necessarily equal to the column space of \\(R\\), because the reduced row echelon form could contain a row of 0's. In such a case, the spans of these two column spaces are different.

\n

For the same reason, we can conclude that the statement (iv) is false. Referring to Theorem 4 in Section 1.4 The Matrix Operation \\(A\\pmb x=\\pmb b\\) (check the \"Common Errors and Warnings\" in the end), \"For each \\(\\pmb b\\) in \\(\\pmb R^m\\), the equation \\(A\\pmb x=\\pmb b\\) has a solution\" is true if and only if \\(A\\) has a pivot position in every row (not column).

\n

The answer is A.

\n\n
\n

Problem 3 (10 points)

\n

\n

Problem 3 Solution

\n
\n

First, we can do row reduction to obtain the row echelon form of the standard matrix \\[\\begin{align}\n&\\begin{bmatrix}1 &a &a+1\\\\2 &a+2 &a-1\\\\2-a &0 &0\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &a &a+1\\\\0 &-a+2 &-a-3\\\\2-a &0 &0\\\\\\end{bmatrix}\\sim\\\\\n\\sim&\\begin{bmatrix}1 &a &a+1\\\\0 &-a+2 &-a-3\\\\0 &a(a-2) &(a+1)(a-2)\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &a &a+1\\\\0 &-a+2 &-a-3\\\\0 &0 &-4a-2\\\\\\end{bmatrix}\n\\end{align}\\]

\n

If \\(a=2\\), the 2nd column is a multiple of the 1st column, so the columns of \\(A\\) are not linearly independent, then the transformation would not be one-to-one (Check Theorem 12 of Section 1.9 The Matrix of a Linear Transformation).

\n

Moreover, if \\(a=-\\frac{1}{2}\\), the entries of the last row are all 0s. In such case, matrix \\(A\\) has only two pivots and \\(A\\pmb x=\\pmb 0\\) has non-trivial solutions, \\(L\\) is not one-to-one (See Theorem 11 of Section 1.9 The Matrix of a Linear Transformation).

\n

So the answer is C.

\n\n
\n

Problem 4 (10 points)

\n

\n

Problem 4 Solution

\n
\n

Statement A is wrong as none of these 3 vectors is a linear combination of the other two. They form a linearly independent set.

\n

Statement B is wrong as we need 4 linearly independent vectors to span \\(\\mathbb R^4\\).

\n

Statements C and D are also wrong because B is wrong. Not all vectors in \\(\\mathbb R^4\\) can be generated with a linear combination of these 3 vectors, and \\(A\\pmb x=\\pmb b\\) might have no solution.

\n

Statements E is correct. It has a unique but trivial solution. Quoted from the textbook Section 1.7 Linear Independence:

\n
\n

The columns of a matrix \\(A\\) are linearly independent if and only if the equation \\(A\\pmb x=\\pmb 0\\) has only the trivial solution.

\n
\n

So the answer is E.

\n\n
\n

Problem 5 (10 points)

\n

\n

Problem 5 Solution

\n
\n

From the given condition, we know that \\(A\\) is a \\(m\\times n\\) matrix. So statement A is wrong.

\n

Statement B is not necessarily true since \\(\\pmb b\\) could be outside of the range but still in the \\(\\mathbb R^m\\) as the codomain of \\(T\\). Statement E is also not true for the same reason.

\n

Statement D is wrong. Since \\(m\\) is the row number of the matrix \\(A\\), rank \\(A=m\\) just means the number of pivots is equal to the row number. To have the column linearly independent, we need the pivot number to be the same as the column number.

\n

Now we have only statement C left. If \\(m<n\\), the column vector set is linearly dependent. But \\(T\\) is one-to-one if and only if the columns of \\(A\\) are linearly independent. So \\(m<n\\) cannot be true.

\n

The answer is C.

\n\n
\n

Problem 6 (10 points)

\n

\n

Problem 6 Solution

\n
\n

This is to solve the following equation system: \\[\n\\begin{bmatrix}2 &3\\\\1 &-1\\\\5 &4\\\\\\end{bmatrix}\n\\begin{bmatrix}x_1\\\\x_2\\\\\\end{bmatrix}=\n\\begin{bmatrix}1\\\\3\\\\6\\\\\\end{bmatrix}\n\\] Let's do the row reduction with the augmented matrix \\[\n\\begin{bmatrix}2 &3 &1\\\\1 &-1 &3\\\\5 &4 &6\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-1 &3\\\\2 &3 &1\\\\5 &4 &6\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-1 &3\\\\0 &5 &-5\\\\0 &9 &-9\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-1 &3\\\\0 &1 &-1\\\\0 &0 &0\\\\\\end{bmatrix}\n\\]

\n

This yields the unique solution \\(x_1=2\\) and \\(x_2=-1\\). So the answer is B.

\n\n
\n

Problem 7 (10 points)

\n

\n

Problem 7 Solution

\n
\n

First, we can exclude E as it has a zero vector, and a vector set including a zero vector is always linearly dependent.

\n

C has its column 2 equal to 2 times column 1. It is not linearly independent.

\n

A is also wrong. It is easy to see that column 3 is equal to 2 times column 1 minus column 2.

\n

B has zeros in row 3 of all four vectors. So all the vectors have only 3 valid entries. But we have 4 vectors. Referring to Theorem 8 of Section 1.7 Linear Independence, this is equivalent to the case that 4 vectors are all in 3D space. So there must be one vector that is a linear combination of the other 3. B is not the right answer.

\n

D can be converted to the vector set \\[\\begin{Bmatrix}\n\\begin{bmatrix}1\\\\1\\\\0\\end{bmatrix},\n\\begin{bmatrix}0\\\\1\\\\1\\end{bmatrix},\n\\begin{bmatrix}1\\\\0\\\\0\\end{bmatrix}\n\\end{Bmatrix}\\] This is a linear independent vector set since we cannot get any column by linearly combining the other two.

\n

So the answer is D.

\n\n
\n

Problem 8 (10 points)

\n

\n

Problem 8 Solution

\n
\n
    \n
  1. Start with the augmented matrix and do row reduction \\[\n\\begin{bmatrix}1 &1 &a &1\\\\0 &1 &a^2-2 &a\\\\3 &2 &2 &3\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &1 &a &1\\\\0 &1 &a^2-2 &a\\\\0 &-1 &2-3a &0\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &1 &a &1\\\\0 &1 &a^2-2 &a\\\\0 &0 &a(a-3) &a\\\\\\end{bmatrix}\n\\]

  2. \n
  3. Apparently if \\(a=0\\), the last row has all zero entries, the system has one free variable and there are an infinite number of solutions.

  4. \n
  5. If \\(a=3\\), the last row indicates \\(0=3\\), the system is inconsistent and has no solution.

  6. \n
  7. If \\(a\\) is neither 3 nor 0, the row echelon form shows three pivots, thus the system has a unique solution.

  8. \n
\n\n
\n

Problem 9 (10 points)

\n

\n

Problem 9 Solution

\n
\n
    \n
  1. The sequence of row reduction to get the reduced row echelon form is shown below \\[\\begin{align}\n&\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\2 &0 &-3 &-4 &5\\\\5 &0 &-6 &-1 &14\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\0 &0 &-1 &0 &-1\\\\0 &0 &-1 &0 &-1\\\\\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\0 &0 &-1 &0 &-1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\0 &0 &1 &0 &1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0 &-2 &4\\\\0 &0 &1 &0 &1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\n\\end{align}\\]

  2. \n
  3. From the reduced row echelon form, we can see that there are two pivots and three free variables \\(x_2\\), \\(x_4\\), and \\(x_5\\). So the system \\(A\\pmb x=\\pmb 0\\) becomes \\[\\begin{align}\nx_1-2x_4+4x_5&=0\\\\\nx_3+x_5&=0\n\\end{align}\\]

  4. \n
\n

Now write the solution in parametric vector form. The general solution is \\(x_1=2x_4-4x_5\\), \\(x_3=-x_5\\). This can be written as \\[\n \\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n \\begin{bmatrix}2x_4-4x_5\\\\x_2\\\\-x_5\\\\x_4\\\\x_5\\end{bmatrix}=\n x_2\\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix}+\n x_4\\begin{bmatrix}2\\\\0\\\\0\\\\1\\\\0\\end{bmatrix}+\n x_5\\begin{bmatrix}-4\\\\0\\\\-1\\\\0\\\\1\\end{bmatrix}\n \\] So the basis for Nul \\(A\\) is \\[\\begin{Bmatrix}\n \\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix},\n \\begin{bmatrix}2\\\\0\\\\0\\\\1\\\\0\\end{bmatrix},\n \\begin{bmatrix}-4\\\\0\\\\-1\\\\0\\\\1\\end{bmatrix}\n \\end{Bmatrix}\\]

\n\n
\n

Problem 10 (10 points)

\n

\n

Problem 10 Solution

\n
\n
    \n
  1. For computing the determinant of matrix \\(A\\) with the 1st column cofactor expansion, note that the only nonzero entry in column 1 is \\(a_{1,4}=2\\), so we have \\[\\begin{align}\n\\det A&=(-1)^{1+4}\\cdot 2\\cdot\\begin{vmatrix}1 &2 &3\\\\0 &\\color{fuchsia}3 &0\\\\1 &1 &1\\end{vmatrix}\\\\\n &=(-2)\\cdot 3\\begin{vmatrix}1 &3\\\\1 &1\\end{vmatrix}=(-6)\\cdot(-2)=12\n\\end{align}\\]

  2. \n
  3. From the adjugate of \\(A\\), we deduce the formula

  4. \n
\n

\\[\\begin{align}\nb_{3,2}&=\\frac{C_{2,3}}{\\det A}=\\frac{1}{12}\\cdot(-1)^{2+3}\\begin{vmatrix}0 &1 &3\\\\0 &1 &1\\\\\\color{fuchsia}2 &0 &1\\end{vmatrix}\\\\\n&=\\frac{-1}{12}\\cdot(-1)^{3+1}\\cdot 2\\begin{vmatrix}1 &3\\\\1 &1\\end{vmatrix}=\\frac{1}{3}\n\\end{align}\\]

\n\n
\n

Exam Summary

\n

Here is the table listing the key knowledge points for each problem in this exam:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Problem #Points of Knowledge
1Matrix Multiplications, Inverse Matrix
2Column Space, Rank, Nul Space, Determinant, Pivot, Linear System Consistency
3Linear Transformation, One-to-One Mapping
4Linear Dependency, Vector Set Span \\(\\mathbb R^n\\), Unique Solution
5Linear Transformation, One-to-One Mapping, Rank, Column Linear Independency, Vector Set Span \\(\\mathbb R^n\\)
6Basis of Span \\({v_1, v_2}\\)
7Linear Independency Vector Set
8Row Echelon Form, Augmented Matrix, Linear System Solution Set and Consistency
9Reduced Row Echelon Form, Basis for the Null Space
10Determinant, Cofactor Expansion, Inverse Matrix, The Adjugate of Matrix
\n

As can be seen, it has a good coverage of the topics of the specified sections from the textbook. Students should carefully review those to prepare for this and similar exams.

\n

Common Errors and Warnings

\n

Here are a few warnings collected from the textbook. It is highly recommended that students preparing for the MA 265 Midterm I exam review these carefully to identify common errors and know how to prevent them in the test.

\n

The Matrix Equation

\n

\n

\n

Solution Sets of Linear System

\n

\n

\n

Linear Independence

\n

\n

\n

Matrix Operations

\n

\n

Subspace of \\(\\mathbb R^N\\)

\n

\n

Properties of Determinants

\n

\n

\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Spring 2023 Midterm I Solutions","url":"/en/2024/01/23/Purdue-MA265-2023-Spring-Midterm1/","content":"

This is the 2nd study notes post for the college linear algebra course. Here is the review of Purdue MA 26500 Spring 2023 midterm I. I provide solutions to all exam questions as well as concise explanations.

\n

Matrices act. They don't just sit there.
Gilbert Strang (American mathematician known for his contributions to finite element theory, the calculus of variations, wavelet analysis and linear algebra.)

\n
\n

Introduction

\n

Purdue University Department of Mathematics provides an introductory-level linear algebra course MA 26500 every semester. Undergraduate students of science and engineering majors taking this course would gain a good mathematical foundation for their advanced studies in machine learning, computer graphics, control theory, etc.

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.

\n
\n

MA 26500 midterm I covers the topics of Sections 1.1 – 3.3 in the textbook. It is usually scheduled at the beginning of the seventh week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.

\n

Here are a few extra reference links for Purdue MA 26500:

\n\n

Spring 2023 Midterm I Solutions

\n

Problem 1 (10 points)

\n

\n

Problem 1 Solution

\n
\n

Referring to Section 3.2 Property of Determinants, we can do row and column operations to efficiently find the determinant of the given matrix.

\n

\\[\\begin{align}\n\\begin{vmatrix}a &b &3c\\\\g &h &3i\\\\d+2a &e+2b &3f+6c\\\\\\end{vmatrix}&=(-1)\\cdot\\begin{vmatrix}a &b &3c\\\\d+2a &e+2b &3f+6c\\\\g &h &3i\\\\\\end{vmatrix}\\\\\n&=(-1)\\cdot\\begin{vmatrix}a &b &3c\\\\d &e &3f\\\\g &h &3i\\\\\\end{vmatrix}=\n(-1)\\cdot3\\begin{vmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\\\\\end{vmatrix}\\\\\n&=-3\\cdot 2=-6\n\\end{align}\\]

\n

The exact sequence of the operations are

\n
    \n
  1. An interchange of rows 2 and 3 reverses the sign of the determinant.
  2. \n
  3. Adding -2 times row 1 to row 2 does not change the determinant.
  4. \n
  5. Factoring out a common multiple of column 3.
  6. \n
  7. Applying the known result of det \\(A\\).
  8. \n
\n

So the answer is B.

\n\n
\n

Problem 2 (10 points)

\n

\n

Problem 2 Solution

\n
\n

This problem tests the students' knowledge of rank and dimension. Referring to Section 2.9 Dimension and Rank, we know the following important points:

\n
\n
    \n
  1. Since the pivot columns of \\(A\\) form a basis for Col \\(A\\), the rank of \\(A\\) is just the number of pivot columns in \\(A\\).
  2. \n
  3. If a matrix \\(A\\) has \\(n\\) columns, then rank \\(A\\) + dim Nul \\(A\\) = \\(n\\).
  4. \n
\n
\n

To find out the number of pivot columns in \\(A\\), we can do elementary row operations to obtain the Row Echelon Form of matrix \\(A\\).

\n

\\[\\begin{align}\n&\\begin{bmatrix}1 &2 &2 &5 &0\\\\-2 &0 &-2 &2 &-4\\\\3 &4 &-1 &9 &2\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &2 &5 &0\\\\0 &4 &-4 &12 &-4\\\\0 &-2 &2 &-6 &2\\\\\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &2 &2 &5 &0\\\\0 &1 &-1 &3 &-1\\\\0 &1 &-1 &3 &-1\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}{1} &2 &2 &5 &0\\\\0 &\\color{fuchsia}{1} &-1 &3 &-1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\n\\end{align}\\]

\n

Now it is clear that this matrix has two pivot columns, thus rank \\(A\\) is 2, and dim Nul \\(A\\) is \\(5-2=3\\).

\n

Since \\(5a-3b=5\\times 2-3\\times 3=1\\), the answer is A.

\n\n
\n

Problem 3 (10 points)

\n

\n

Problem 3 Solution

\n
\n

For such linear transformation \\(T:\\mathbb R^3\\to\\mathbb R^3\\), onto means for each \\(\\pmb b\\) in the codomain \\(\\mathbb R^{3}\\), there exists at least one solution of \\(T(\\pmb x)=\\pmb b\\).

\n

Let's do row reduction first to see

\n

\\[\\begin{align}\n&\\begin{bmatrix}1 &t &2\\\\3 &3 &t-5\\\\2 &0 &0\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\3 &3 &t-5\\\\1 &t &2\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\1 &1 &\\frac{t-5}{3}\\\\0 &t &2\\\\\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &0 &0\\\\0 &1 &\\frac{t-5}{3}\\\\0 &t &2\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\0 &1 &\\frac{t-5}{3}\\\\0 &0 &2-\\frac{(t-5)t}{3}\\\\\\end{bmatrix}\n\\end{align}\\]

\n

Now inspect the entry of row 3 and column 3, it can be factorized as \\(\\frac{(6-t)(1+t)}{3}\\). If \\(t\\) is 6 or -1, this entry becomes 0. In such cases, for a nonzero \\(b_{3}\\) of \\(\\pmb b\\) in \\(\\mathbb R^{3}\\), there would be no solution at all.

\n

So to make this linear transformation onto \\(\\mathbb R^{3}\\), \\(t\\) cannot be 6 or -1. The answer is E.

\n\n
\n

Problem 4 (10 points)

\n

\n

Problem 4 Solution

\n
\n

Let's inspect the statements one by one.

\n

For (i), from Section 1.7 Linear Independence, because \\(A\\pmb x=\\pmb 0\\) has only a trivial solution, the columns of the matrix \\(A\\) are linearly independent. So there should be at most one solution for these column vectors to combine and obtain, this statement is true.

\n

Statement (ii) is also true. If \\(m<n\\), according to Theorem 8 of Section 1.7, the set of column vectors is linearly dependent, etc a \\(2\\times 3\\) matrix (see Example 5 of Section 1.7). Then \\(A\\pmb x=\\pmb 0\\) has a nontrivial solution. Now referring to Theorem 11 of Section 1.9, this linear transformation of matrix \\(A\\) is NOT one-to-one.

\n

Thinking of the case \\(3\\times 2\\) for the linear transformation \\(T: \\mathbb R^2\\to\\mathbb R^3\\), we can get one-to-one mapping. But for \\(T: \\mathbb R^3\\to\\mathbb R^2\\), there could be more than 1 point in 3D space mapping to a 2D point. It is not one-to-one.

\n

For (iii), certainly this is not true. A simple example can be a \\(3\\times 2\\) matrix like below \\[\\begin{bmatrix}1 &0\\\\1 &1\\\\0 &1\\\\\\end{bmatrix}\\] The two columns above are NOT linearly dependent.

\n

Statement (iv) is true as this is the exact case described by Theorem 4 (c) and (d) in Section 1.4.

\n

The answer is D.

\n\n
\n

Problem 5 (10 points)

\n

\n

Problem 5 Solution

\n
\n

From the given conditions, we know that the columns of \\(A\\) form a linearly dependent set. Equivalently this means \\(A\\) is not invertible and \\(A\\pmb x=\\pmb 0\\) has two nontrivial solutions \\[\\begin{align}\nA\\pmb x&=[\\pmb a_{1}\\,\\pmb a_{2}\\,\\pmb a_{3}\\,\\pmb a_{4}\\,\\pmb a_{5}]\\begin{bmatrix}5\\\\1\\\\-6\\\\-2\\\\0\\end{bmatrix}=\\pmb 0\\\\\nA\\pmb x&=[\\pmb a_{1}\\,\\pmb a_{2}\\,\\pmb a_{3}\\,\\pmb a_{4}\\,\\pmb a_{5}]\\begin{bmatrix}0\\\\2\\\\-7\\\\1\\\\3\\end{bmatrix}=\\pmb 0\\\\\n\\end{align}\\] So Statement E is false. Moveover, a noninvertible \\(A\\) has \\(\\det A = 0\\). The statement A is false too.

\n

The two nontrivial solutions for \\(A\\pmb x=\\pmb 0\\) are \\([5\\,\\,1\\,\\,-6\\,\\,-2\\,\\,0]^T\\) and \\([0\\,\\,2\\,\\,-7\\,\\,1\\,\\,-3]^T\\). As they are also linear independent as one is not a multiple of the other, they should be in the basis for Nul \\(A\\). But we are not sure if there are also other vectors in the basis. We can only deduce that dim Nul \\(A\\) is at least 2. From this, we decide that statement B is false.

\n

Again because rank \\(A\\) + dim Nul \\(A\\) = \\(5\\), and dim Nul \\(A\\) is greater than or equal to 2, rank \\(A\\) must be less than or equal to 3. Statement C is true.

\n

Statement D is not true either, since \\([1\\,\\,2\\,\\,-7\\,\\,1\\,\\,-3]^T\\) is not a linear combination of \\([5\\,\\,1\\,\\,-6\\,\\,-2\\,\\,0]^T\\) and \\([0\\,\\,2\\,\\,-7\\,\\,1\\,\\,-3]^T\\).

\n

So the answer is C.

\n\n
\n

Problem 6 (10 points)

\n

\n

Problem 6 Solution

\n
\n

Denote the adjugate of \\(A\\) as \\(B=\\{b_{ij}\\}\\), then \\(b_{ij}=C_{ji}\\), where \\(C_{ji}\\) is the cofactor of \\(A\\). Compute two non-corner entries of \\(B\\) below \\[\\begin{align}\nb_{12}&=C_{21}=(-1)^{2+1}\\begin{vmatrix}0 &-1\\\\1 &-1\\end{vmatrix}=-1\\\\\nb_{21}&=C_{12}=(-1)^{1+2}\\begin{vmatrix}-5 &-1\\\\3 &-1\\end{vmatrix}=-8\n\\end{align}\\]

\n

So the answer is C.

\n\n
\n

Problem 7 (10 points)

\n

\n

Problem 7 Solution

\n
\n

We need a set of 4 linearly independent vectors to span \\(\\mathbb R^4\\).

\n

Answer A contains the zero vector, thus the set is not linearly independent.

\n

Answer E contains only 3 vectors, not enough as the basis of \\(\\mathbb R^4\\).

\n

Answer D column 3 is 2 times column 2, and column 5 is equal to column 2 and column 4. So it has only 3 linearly independent vectors. Still not enough

\n

Answer C is also not correct. If we scale 1/3 to column 1, and then add it with columns 2 and 3 altogether, it results in column 4. So only 3 linearly independent vectors.

\n

So the answer is B. Indeed B has 4 linearly independent vectors.

\n\n
\n

Problem 8 (10 points)

\n

\n

Problem 8 Solution

\n
\n

This problem is very similar to Problem 8 of Fall 2022 Midterm I. The solution follows the same steps.

\n
    \n
  1. Referring to Theorem 10 of Section 1.9 The Matrix of a Linear Transformation, remember the property \\[T(c\\pmb u+d\\pmb v)=cT(\\pmb u)+dT(\\pmb v)\\] We can use this property to find \\(A\\).

    \n

    First, denote \\(\\pmb u=\\begin{bmatrix}1\\\\1\\end{bmatrix}\\) and \\(\\pmb v=\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\). It is trivial to see that \\[\\begin{align}\n \\pmb{u}&=1\\cdot\\begin{bmatrix}1\\\\0\\end{bmatrix}+1\\cdot\\begin{bmatrix}0\\\\1\\end{bmatrix}=\\pmb{e}_1+\\pmb{e}_2\\\\\n \\pmb{v}&=-1\\cdot\\begin{bmatrix}1\\\\0\\end{bmatrix}+1\\cdot\\begin{bmatrix}0\\\\1\\end{bmatrix}=-\\pmb{e}_1+\\pmb{e}_2\\\\\n \\end{align}\\] This leads to \\[\\begin{align}\n \\pmb{e}_1&=\\begin{bmatrix}1\\\\0\\end{bmatrix}\n =\\frac{1}{2}\\pmb{u}-\\frac{1}{2}\\pmb{v}\\\\\n \\pmb{e}_2&=\\begin{bmatrix}0\\\\1\\end{bmatrix}\n =\\frac{1}{2}\\pmb{u}+\\frac{1}{2}\\pmb{v}\n \\end{align}\\] Then apply the property and compute \\[\\begin{align}\n T(\\pmb{e}_1)&=\\frac{1}{2}T(\\pmb{u})-\\frac{1}{2}T(\\pmb{v})\n =\\frac{1}{2}T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)-\\frac{1}{2}T\\left(\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\right)=\\begin{bmatrix}2\\\\3\\\\\\end{bmatrix}\\\\\n T(\\pmb{e}_2)&=\\frac{1}{2}T(\\pmb{u})+\\frac{1}{2}T(\\pmb{v})\n =\\frac{1}{2}T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)+\\frac{1}{2}T\\left(\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\right)=\\begin{bmatrix}1\\\\1\\end{bmatrix}\n \\end{align}\\]

  2. \n
  3. We know that the standard matrix is \\[A=[T(\\pmb{e}_1)\\quad\\dots\\quad T(\\pmb{e}_n)]\\] as we have \\(T(\\pmb{e}_1)\\) and \\(T(\\pmb{e}_2)\\) now, the standard matrix \\(A\\) is \\(\\begin{bmatrix}2 &1\\\\3 &1\\end{bmatrix}\\). It is a \\(2\\times 2\\) matrix. The inverse formula is (see Theorem 4 in Section 2.2 The Inverse of A Matrix) \\[\\begin{align}\n A&=\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}\\\\\n A^{-1}&=\\frac{1}{ad-bc}\\begin{bmatrix}d &-b\\\\-c &a\\end{bmatrix}\\\\\n \\end{align}\\] This yields \\(A^{-1}=\\begin{bmatrix}-1 &1\\\\3 &-2\\end{bmatrix}\\).

  4. \n
  5. This is the case of \\(A\\pmb x=\\pmb b\\) and we need to solve it. The augmented matrix here is \\(\\begin{bmatrix}2 &1 &7\\\\3 &1 &9\\end{bmatrix}\\). After row reduction, it becomes \\(\\begin{bmatrix}0 &1 &3\\\\1 &0 &2\\end{bmatrix}\\). This has unique solution \\(\\pmb x=\\begin{bmatrix}2\\\\3\\\\\\end{bmatrix}\\).

  6. \n
\n

📝Notes:The students should remeber the inverse formula of \\(2\\times 2\\) matrix!

\n\n
\n

Problem 9 (10 points)

\n

\n

Problem 9 Solution

\n
\n

This problem is also very similar to Problem 9 of Fall 2022 Midterm I. The solution follows the same steps.

\n
    \n
  1. The augmented matrix and the row reduction results can be seen below \\[\n\\begin{bmatrix}1 &0 &-1 &1\\\\1 &1 &h-1 &3\\\\0 &2 &h^2-3 &h+1\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &1\\\\0 &1 &h &2\\\\0 &2 &h^2-3 &h+1\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &1\\\\0 &1 &h &2\\\\0 &0 &a^2-2h-3 &h-3\\\\\\end{bmatrix}\n\\] The pivots are \\(1\\), \\(1\\), and \\(a^2-2h-3\\).

  2. \n
  3. When \\(h=3\\), the last row entries become all zeros. This system has an infinite number of solutions.

  4. \n
  5. If \\(h=-1\\), last row becomes \\([0\\,0\\,0\\,-4]\\). Now the system is inconsistent and has no solution.

  6. \n
  7. If \\(h\\) is not 3 or -1, last row becomes \\([0\\,0\\,h+1\\,1]\\). We get \\(z=\\frac{1}{h+1}\\). The system has a unique solution.

  8. \n
\n\n
\n

Problem 10 (10 points)

\n

\n

Problem 10 Solution

\n
\n

This problem is also very similar to Problem 10 of Fall 2022 Midterm I. The solution follows the same steps.

\n
    \n
  1. The row reduction is completed next. The symbol ~ before a matrix indicates that the matrix is row equivalent to the preceding matrix.
  2. \n
\n

\\[\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\1 &0 &5 &13 &20\\\\2 &0 &4 &12 &22\\\\3 &0 &2 &0 &21\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &3 &9 &9\\\\1 &0 &2 &6 &11\\\\0 &0 &-4 &-12 &-12\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &1 &3 &3\\\\0 &0 &0 &2 &0\\\\0 &0 &1 &3 &3\\end{bmatrix}\n\\] \\[\n\\sim\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &1 &3 &3\\\\0 &0 &0 &1 &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &1 &0 &3\\\\0 &0 &0 &1 &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}{1} &0 &0 &0 &5\\\\0 &0 &\\color{fuchsia}{1} &0 &3\\\\0 &0 &0 &\\color{fuchsia}{1} &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\n\\]

\n
    \n
  1. Referring to Theorem 12 Section 2.8 Matrix Algebra and the Warning message below that (quoted below)

    \n
    \n

    Warning: Be careful to use pivot columns of \\(A\\) itself for the basis of Col \\(A\\). Thecolumns of an echelon form \\(B\\) are often not in the column space of \\(A\\).

    \n
    \n

    So the pivot columns of the original matrix \\(A\\) form a basis for the column space of \\(A\\). The basis is the set of columns 1, 3, and 4. \\[\n \\begin{Bmatrix}\\begin{bmatrix}1\\\\1\\\\2\\\\3\\end{bmatrix},\n \\begin{bmatrix}2\\\\5\\\\4\\\\2\\end{bmatrix},\n \\begin{bmatrix}4\\\\13\\\\12\\\\0\\end{bmatrix}\\end{Bmatrix}\n \\]

  2. \n
  3. Referring to Section 2.8 Subspaces of \\(\\mathbb R^n\\), by definition the null space of a matrix \\(A\\) is the set Nul \\(A\\) of all solutions of the homogeneous equation \\(A\\pmb{x}=\\pmb{0}\\). Also \"A basis for a subspace \\(H\\) of \\(\\mathbb R^n\\) is a linearly independent set in \\(H\\) that spans \\(H\\)\".

    \n

    Now write the solution of \\(A\\mathrm x=\\pmb 0\\) in parametric vector form \\[[A\\;\\pmb 0]\\sim\\begin{bmatrix}\\color{fuchsia}{1} &0 &0 &0 &5 &0\\\\0 &0 &\\color{fuchsia}{1} &0 &3 &0\\\\0 &0 &0 &\\color{fuchsia}{1} &0 &0\\\\0 &0 &0 &0 &0 &0\\end{bmatrix}\\]

    \n

    The general solution is \\(x_1=-5x_5\\), \\(x_3=-3x_5\\), \\(x_4=0\\), with \\(x_2\\) and \\(x_5\\) free. This can be written as \\[\n \\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n \\begin{bmatrix}-5x_5\\\\x_2\\\\-3x_5\\\\0\\\\x_5\\end{bmatrix}=\n x_4\\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix}+\n x_5\\begin{bmatrix}-5\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\n \\] So the basis for Nul \\(A\\) is \\[\n \\begin{Bmatrix}\\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix},\n \\begin{bmatrix}-5\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\n \\]

  4. \n
\n\n
\n

Summary

\n

Here is the table listing the key knowledge points for each problem in this exam:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Problem #Points of Knowledge
1Determinant and its Properties
2Rank and Dimension of the Null Space of a Matrix, Pivot Columns, Row Reduction Operation
3Linear Transformation, Onto \\(\\mathbb R^m\\), Linear System Consistency
4Homogeneous Linear Systems, One-to-One Mapping Linear Transformation, the Column Space of the Matrix
5Linear Dependency, Invertible Matrix, Determinant, Rank and Dimension of the Null Space of Matrix
6The Adjugate of Matrix, The (\\(i,j\\))-cofactor of Matrix
7Linear Independency, Vector Set Spanning Space \\(\\mathbb R^n\\)
8Linear Transformation Properties, Standard Matrix for a Linear Transformation
9Row Echelon Form, Linear System Solution Set and Consistency
10Reduced Row Echelon Form, Basis for the Column Vector Space and the Null Space
\n

As can be seen, it has a good coverage of the topics of the specified sections from the textbook. Students should carefully review those to prepare for this and similar exams.

\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Spring 2023 Midterm II Solutions","url":"/en/2024/02/29/Purdue-MA265-2023-Spring-Midterm2/","content":"

Here comes the solution and analysis for Purdue MA 26500 Spring 2023 Midterm II. This second midterm covers topics in Chapter 4 (Vector Spaces) and Chapter 5 (Eigenvalues and Eigenvectors) of the textbook.

\n

Introduction

\n

Purdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.

\n

Textbook and Study Guide

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.

\n
\n

Exam Information

\n

MA 26500 midterm II covers the topics of Sections 4.1 – 5.7 in the textbook. It is usually scheduled at the beginning of the thirteenth week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.

\n

Based on the knowledge of linear equations and matrix algebra learned in the book chapters 1 and 2, Chapter 4 leads the student to a deep dive into the vector space framework. Chapter 5 introduces the important concepts of eigenvectors and eigenvalues. They are useful throughout pure and applied mathematics. Eigenvalues are also used to study differential equations and continuous dynamical systems, they provide critical information in engineering design,

\n

Reference Links

\n\n

Spring 2023 Midterm II Solutions

\n

Problem 1 (10 points)

\n

\n

Problem 1 Solution

\n
\n

A For \\(5\\times 7\\) matrix, if \\(rank(A)=5\\), the dimension of the null space is \\(7-5=2\\). So this is wrong.

\n

B The matrix has 7 columns, but there are only 5 pivot columns, so the columns of \\(A\\) are NOT linearly independent. It is wrong.

\n

C \\(A^T\\) is a \\(7\\times 5\\) matrix, and the rank of \\(A^T\\) is no more than 5. This statement is wrong.

\n

D Because there are 5 pivots, each row has one pivot. Thus the rows of \\(A\\) are linearly independent. This statement is TRUE.

\n

E From statement D, it can be deduced that the dimension of the row space is 5, not 2.

\n

The answer is D.

\n\n
\n

Problem 2 (10 points)

\n

\n

Problem 2 Solution

\n
\n

The vector in this subspace \\(H\\) can be represented as \\[\na\\begin{bmatrix}1\\\\1\\\\0\\\\0\\end{bmatrix}+\nb\\begin{bmatrix}-2\\\\-1\\\\1\\\\0\\end{bmatrix}+\nc\\begin{bmatrix}9\\\\6\\\\-3\\\\0\\end{bmatrix}+\nd\\begin{bmatrix}5\\\\5\\\\1\\\\5\\end{bmatrix}+\ne\\begin{bmatrix}4\\\\-3\\\\-9\\\\-10\\end{bmatrix}\n\\]

\n

Here the transformation matrix \\(A\\) has 5 columns and each has 4 entries. Hence these column vectors are not linearly independent.

\n
\n

Note that row operations do not affect the dependence relations between the column vectors. This makes it possible to use row reduction to find a basis for the column space.

\n
\n

\\[\n\\begin{align}\n&\\begin{bmatrix}1 &-2 &9 &5 &4\\\\1 &-1 &6 &5 &-3\\\\0 &1 &-3 &1 &-9\\\\0 &0 &0 &5 &-10\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-2 &9 &5 &4\\\\0 &1 &-3 &0 &-7\\\\0 &1 &-3 &1 &-9\\\\0 &0 &0 &5 &-10\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &-2 &9 &5 &4\\\\0 &1 &-3 &0 &-7\\\\0 &0 &0 &1 &-2\\\\0 &0 &0 &5 &-10\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}1 &-2 &9 &5 &4\\\\0 &\\color{fuchsia}1 &-3 &0 &-7\\\\0 &0 &0 &\\color{fuchsia}1 &-2\\\\0 &0 &0 &0 &0\\end{bmatrix}\n\\end{align}\n\\]

\n

The dimension of \\(H\\) is the number of linearly independent columns of the matrix, which is the number of pivots in \\(A\\)'s row echelon form. So the dimension is 3.

\n

The answer is C.

\n\n
\n

Problem 3 (10 points)

\n

\n

Problem 3 Solution

\n
\n

First, find the eigenvalues for the matrix \\[\n\\begin{align}\n\\det A-\\lambda I &=\\begin{vmatrix}2-\\lambda &2\\\\3 &1-\\lambda\\end{vmatrix}=(\\lambda^2-3\\lambda+2)-6\\\\&=\\lambda^2-3\\lambda-4=(\\lambda+1)(\\lambda-4)=0\n\\end{align}\n\\] The above gives two real eigenvalues \\(-1\\) and \\(4\\). Since they have opposite signs, the origin is a saddle point.

\n

The answer is A.

\n\n
\n

Problem 4 (10 points)

\n

\n

Problem 4 Solution

\n
\n

(i) is NOT true. Referring to Theorem 4 of Section 5.2 \"The Characteristic Equation\",

\n
\n

If \\(n\\times n\\) matrices \\(A\\) and \\(B\\) are similar, then they have the same characteristic polynomial and hence the same eigenvalues (with the same multiplicities).

\n
\n

But the reverse statement is NOT true. They are matrices that are not similar even though they have the same eigenvalues.

\n

(ii) is NOT true either. Referring to Theorem 6 of Section 5.3 \"Diagonalization\",

\n
\n

An \\(n\\times n\\) matrix with \\(n\\) distinct eigenvalues is diagonalizable.

\n
\n

The book mentions that the above theorem provides a sufficient condition for a matrix to be diagonalizable. So the reverse statement is NOT true. There are examples that a diagonalizable matrix has eigenvalues with multiplicity 2 or more.

\n

(iii) Since the identity matrix is symmetric, and \\(\\det A=\\det A^T\\) for \\(n\\times n\\) matrix, we can write \\(\\det (A-\\lambda I) = \\det (A-\\lambda I)^T = \\det(A^T-\\lambda I)\\). So matrix \\(A\\) and its transpose have the same eigenvalues. This statement is TRUE.

\n

(iv) This is definitely TRUE as we can find eigenvectors that are linearly independent and span \\(\\mathbb R^n\\).

\n

(v) If matrix \\(A\\) has zero eigenvalue, \\(\\det A-0I=\\det A=0\\), it is not invertible. This statement is TRUE.

\n

In summary, statements (iii), (iv), and (v) are TRUE. The answer is E.

\n\n
\n

Problem 5 (10 points)

\n

\n

Problem 5 Solution

\n
\n

A This vector set does not include zero vector (\\(x = y = 0\\)). So it is not a subspace of \\(V\\).

\n

B For eigenvalue 3, we can find out the eigenvector from \\(\\begin{bmatrix}0 &0\\\\2 &0\\end{bmatrix}\\pmb v=\\pmb 0\\), it is \\(\\begin{bmatrix}0\\\\\\ast\\end{bmatrix}\\). All vectors in this set satisfy three subspace properties. So this one is good.

\n

C This cannot be the right choice. Since the 3rd entry is always 1, the vector set cannot be closed under vector addition and multiplication by scalars. Also, it does not include zero vector either.

\n

D For \\(p(x)=a_0+a_1x+a_2x^2\\) and \\(p(1)p(2)=0\\), this gives \\[(a_0+a_1+a_2)(a_0+2a_1+4a_2)=0\\] To verify if this is closed under vector addition. Define \\(q(x)=b_0+b_1x+b_2x^2\\) that has \\(q(1)q(2)=0\\), this gives \\[(b_0+b_1+b_2)(b_0+2b_1+4b_2)=0\\] Now let \\(r(x)=p(x)+q(x)=c_0+c_1x+c_2x^2\\), where \\(c_i=a_i+b_i\\) for \\(i=0,1,2\\). Is it true that \\[(c_0+c_1+c_2)(c_0+2c_1+4c_2)=0\\] No, it is not necessarily the case. This one is not the right choice either.

\n

E Invertible matrix indicates that its determinant is not 0. The all-zero matrix is certainly not invertible, so it is not in the specified set. Moreover, two invertible matrices can add to a non-invertible matrix, such as the following example \\[\n\\begin{bmatrix}2 &1\\\\1 &2\\end{bmatrix}+\\begin{bmatrix}-2 &1\\\\-1 &-2\\end{bmatrix}=\\begin{bmatrix}0 &2\\\\0 &0\\end{bmatrix}\n\\] This set is NOT a subspace of \\(V\\).

\n

The answer is B.

\n\n
\n

Problem 6 (10 points)

\n

\n

Problem 6 Solution

\n
\n

Recall from the Problem 4 solution that a matrix with \\(n\\) distinct eigenvalues is diagonalizable.

\n

(i) The following calculation shows this matrix has two eigenvalues 4 and 1. So it is diagonalizable. \\[\\begin{vmatrix}2-\\lambda &2\\\\1 &3-\\lambda\\end{vmatrix}=(\\lambda^2-5\\lambda+6)-2=(\\lambda-1)(\\lambda-4)=0\\]

\n

(ii) It is easy to see that there is one eigenvalue \\(-3\\) with multiplicity 2. However, we can only get one eigenvector \\(\\begin{bmatrix}1\\\\0\\end{bmatrix}\\) for such eigenvalue. So it is NOT diagonalizable.

\n

(iii) To find out the eigenvalues for this \\(3\\times 3\\) matrix, do the calculation as below \\[\n\\begin{vmatrix}2-\\lambda &3 &5\\\\0 &2-\\lambda &1\\\\0 &1 &2-\\lambda\\end{vmatrix}=(2-\\lambda)\\begin{vmatrix}2-\\lambda &1\\\\1 &2-\\lambda\\end{vmatrix}=(2-\\lambda)(\\lambda-3)(\\lambda-1)\n\\] So we get 3 eigenvalues 2, 3, and 1. This matrix is diagonalizable.

\n

(iv) This is an upper triangular matrix, so the diagonal entries (5, 4, 2) are all eigenvalues. As this matrix has three distinct eigenvalues, it is diagonalizable.

\n

Since only (ii) is not diagonalizable, the answer is E.

\n\n
\n

Problem 7 (10 points)

\n

\n

Problem 7 Solution

\n
\n

This problem involves complex eigenvalues.

\n

Step 1: Find the eigenvalue of the given matrix \\[\n\\begin{vmatrix}1-\\lambda &-1\\\\1 &1-\\lambda\\end{vmatrix}=\\lambda^2-2\\lambda+2=0\n\\] Solve this with the quadratic formula \\[\n\\lambda=\\frac {-b\\pm {\\sqrt {b^{2}-4ac}}}{2a}=\\frac {-(-2)\\pm {\\sqrt {(-2)^2-4\\times 1\\times 2}}}{2\\times 1}=1\\pm i\n\\]

\n

Step 2: Find the corresponding eigenvector for \\(\\lambda=1+i\\) \\[\n\\begin{bmatrix}-i &-1\\\\1 &-i\\end{bmatrix}\\sim\\begin{bmatrix}0 &0\\\\1 &-i\\end{bmatrix}\n\\] This gives \\(x_1=ix_2\\), so the eigervector can be \\(\\begin{bmatrix}i\\\\1\\end{bmatrix}\\).

\n

Step 3: Generate the real solution

\n

From Section 5.7 \"Applications to Differential Equations\", we learn that the general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] For a real matrix, complex eigenvalues and associated eigenvectors come in conjugate pairs. The real and imaginary parts of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) are (real) solutions of \\(\\pmb x'(t)=A\\pmb x(t)\\), because they are linear combinations of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) and \\(\\pmb{v}_2 e^{\\lambda_2 t}\\). (See the proof in \"Complex Eigenvalues\" of Section 5.7)

\n

Now use Euler's formula (\\(e^{ix}=\\cos x+i\\sin x\\)), we have \\[\\pmb{v}_1 e^{\\lambda_1 t}=e^t(\\cos t+i\\sin t)\\begin{bmatrix}i\\\\1\\end{bmatrix}\\\\\n=e^t\\begin{bmatrix}-\\sin t+i\\cos t\\\\\\cos t+i\\sin t\\end{bmatrix}\\] The general REAL solution is the linear combination of the REAL and IMAGINARY parts of the result above, it is \\[c_1 e^t\\begin{bmatrix}-\\sin t\\\\\\cos t\\end{bmatrix}+\nc_2 e^t\\begin{bmatrix}\\cos t\\\\\\sin t\\end{bmatrix}\\]

\n

At first glance, none on the list matches our answer above. However, let's inspect this carefully. We can exclude C and D first since they both have \\(e^{-t}\\) that is not in our answer. Next, it is impossible to be E because it has no minus sign.

\n

Now between A and B, which one is most likely to be the right one? We see that B has \\(-\\cos t\\) on top of \\(\\sin t\\). That could not match our answer no matter what \\(c_2\\) is. If we switch \\(c_1\\) and \\(c_2\\) of A and inverse the sign of the 2nd vector, A would become the same as our answer. Since \\(c_1\\) and \\(c_2\\) are just scalars, this deduction is reasonable.

\n

So the answer is A.

\n\n
\n

Problem 8 (10 points)

\n

\n

Problem 8 Solution

\n
\n

(1) Directly apply \\(p(t)=t^2-1\\) to the mapping function \\[T(t^2-1)=0^2-1+(1^2-1)t+(2^2-1)t^2=-1+3t^2\\]

\n

(2) Denote \\(p(t)=a_0+a_1t+a_2t^2\\), \\(T(p(t))=b_0+b_1t+b_2t^2\\), then \\[\nT(a_0+a_1t+a_2t^2)=a_0+(a_0+a_1+a_2)t+(a_0+2a_1+4a_2)t^2\n\\] So \\[\n\\begin{align}\na_0 &&=b_0\\\\\na_0 &+ a_1 + a_2 &=b_1\\\\\na_0 &+ 2a_1 + 4a_2 &=b_2\n\\end{align}\n\\] This gives the \\([T]_B=\\begin{bmatrix}1 &0 &0\\\\1 &1 &1\\\\1 &2 &4\\end{bmatrix}\\).

\n

Alternatively, we can form the same matrix with the transformation of each base vector: \\[\\begin{align}\nT(1)&=1+t+t^2 => \\begin{bmatrix}1\\\\1\\\\1\\end{bmatrix}\\\\\nT(t)&=0+t+2t^2 => \\begin{bmatrix}0\\\\1\\\\2\\end{bmatrix}\\\\\nT(t^2)&=0+t+4t^2 => \\begin{bmatrix}0\\\\1\\\\4\\end{bmatrix}\\\n\\end{align}\\]

\n\n
\n

Problem 9 (10 points)

\n

\n

Problem 9 Solution

\n
\n

(1) Find the eigenvalues with \\(\\det (A-\\lambda I)=0\\) \\[\n\\begin{vmatrix}1-\\lambda &2 &-1\\\\0 &3-\\lambda &-1\\\\0 &-2 &2-\\lambda\\end{vmatrix}=(1-\\lambda)\\begin{vmatrix}3-\\lambda &-1\\\\-2 &2-\\lambda\\end{vmatrix}=(1-\\lambda)(\\lambda-4)(\\lambda-1)\n\\] So there are \\(\\lambda_1=\\lambda_2=1\\), and \\(\\lambda_3=4\\).

\n

Next is to find the eigenvectors for each eigenvalue

\n
    \n
  • For \\(\\lambda_1=\\lambda_2=1\\), apply row reduction to the agumented matrix of the system \\((A-\\lambda I)\\pmb x=\\pmb 0\\) \\[\n\\begin{bmatrix}0 &2 &-1 &0\\\\0 &2 &-1 &0\\\\0 &-2 &1 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}0 &2 &-1 &0\\\\0 &0 &0 &0\\\\0 &0 &0 &0\\end{bmatrix}\n\\] With two free variables \\(x_1\\) and \\(x_2\\), we get \\(x_3=2x_2\\). So the parametric vector form can be written as \\[\n\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\end{bmatrix}=\nx_1\\begin{bmatrix}1\\\\0\\\\0\\end{bmatrix}+x_2\\begin{bmatrix}0\\\\1\\\\2\\end{bmatrix}\n\\] So the eigenvectors are \\(\\begin{bmatrix}1\\\\0\\\\0\\end{bmatrix}\\) and \\(\\begin{bmatrix}0\\\\1\\\\2\\end{bmatrix}\\).

  • \n
  • For \\(\\lambda_3=4\\), follow the same process \\[\n\\begin{bmatrix}-3 &2 &-1 &0\\\\0 &-1 &-1 &0\\\\0 &-2 &-2 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}3 &-2 &1 &0\\\\0 &1 &1 &0\\\\0 &0 &0 &0\\end{bmatrix}\n\\] With one free variable \\(x_3\\), we get \\(x_1=x_2=-x_3\\). So the eigenvector can be written as \\(\\begin{bmatrix}1\\\\1\\\\-1\\end{bmatrix}\\) (or \\(\\begin{bmatrix}-1\\\\-1\\\\1\\end{bmatrix}\\)).

  • \n
\n

(2) We can directly construct \\(P\\) from the vectors in last step, and construct \\(D\\) from the corresponding eigenvalues. Here are the answers: \\[\nP=\\begin{bmatrix}\\color{fuchsia}1 &\\color{fuchsia}0 &\\color{blue}1\\\\\\color{fuchsia}0 &\\color{fuchsia}1 &\\color{blue}1\\\\\\color{fuchsia}0 &\\color{fuchsia}2 &\\color{blue}{-1}\\end{bmatrix},\\;\nD=\\begin{bmatrix}\\color{fuchsia}1 &0 &0\\\\0 &\\color{fuchsia}1 &0\\\\0 &0 &\\color{blue}4\\end{bmatrix}\n\\]

\n\n
\n

Problem 10 (10 points)

\n

\n

Problem 10 Solution

\n
\n

(1) Find the eigenvalues with \\(\\det (A-\\lambda I)=0\\)
\n\\[\\begin{vmatrix}-4-\\lambda &-5\\\\2 &3-\\lambda\\end{vmatrix}=(\\lambda^2+\\lambda-12)+10=(\\lambda+2)(\\lambda-1)=0\\] So there are two eigervalues \\(-2\\) and 1. Next is to find the eigenvectors for each eigenvalue.

\n

For \\(\\lambda=-2\\), the matrix becomes \\[\\begin{bmatrix}-2 &-5\\\\2 &5\\end{bmatrix}=\\begin{bmatrix}0 &0\\\\2 &5\\end{bmatrix}\\] This yields eigen vector \\(\\begin{bmatrix}5\\\\-2\\end{bmatrix}\\).

\n

For \\(\\lambda=1\\), the matrix becomes \\[\\begin{bmatrix}-5 &-5\\\\2 &2\\end{bmatrix}=\\begin{bmatrix}0 &0\\\\1 &1\\end{bmatrix}\\] This yields eigen vector \\(\\begin{bmatrix}1\\\\-1\\end{bmatrix}\\).

\n

(2) The general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] So from this, since we already found out the eigenvalues and the corresponding eigenvectors, we can write down \\[\n\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}=c_1\\begin{bmatrix}5\\\\-2\\end{bmatrix}e^{-2t}+c_2\\begin{bmatrix}1\\\\-1\\end{bmatrix}e^t\n\\]

\n

(3) Apply the initial values of \\(x(0)\\) and \\(y(0)\\), here comes the following equations: \\[\\begin{align}\n5c_1+c_2&=-3\\\\\n-2c_1-c_2&=0\n\\end{align}\\] This gives \\(c_1=-1\\) and \\(c_2=2\\). So \\(x(1)+y(1)=-5e^{-2}+2e^1+2e^{-2}-2e^{-1}=-3e^{-2}\\).

\n\n
\n

Summary

\n

Here are the key knowledge points covered by this exam:

\n
    \n
  • Linear dependency, Rank, and dimension of null space
  • \n
  • Vector Space, Subspace Properties, and Basis
  • \n
  • Eigenvalues, eigenvectors, and the origin graph
  • \n
  • Similar matrices and diagonalization
  • \n
  • Applications to Differential Equations
  • \n
\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Implement Textbook RSA in Python","url":"/en/2022/01/22/Python-Textbook-RSA/","content":"

RSA encryption algorithm is one of the core technologies of modern public-key cryptography and is widely used on the Internet. As a classical algorithm of public-key cryptography, the programming implementation of textbook RSA can help us quickly grasp its mathematical mechanism and design ideas, and accumulate important experience in the software implementation of cryptography. Here is a detailed example of textbook RSA implementation in Python 3.8 programming environment.

\n

Random numbers should not be generated with a method chosen at random.
Donald Knuth(American computer scientist, mathematician, and professor emeritus at Stanford University, the 1974 recipient of the ACM Turing Award, often called the \"father of the analysis of algorithms\")

\n
\n

Generating Large Primes

\n

The security of the RSA encryption algorithm is built on the mathematical challenge of factoring the product of two large prime numbers. The first step in constructing the RSA encryption system is to generate two large prime numbers \\(p\\) and \\(q\\), and calculate the modulus \\(N=pq\\). \\(N\\) is the length of the RSA key, the larger the more secure. Nowadays, practical systems require the key length to be no less than 2048 bits, with corresponding \\(p\\) and \\(q\\) about 1024 bits each.

\n

A general effectiveness method for generating such large random prime numbers is a probability-based randomization algorithm, which proceeds as follows:

\n
    \n
  1. Pre-select random numbers of given bit length
  2. \n
  3. Do a primality test with small prime numbers (Sieve of Eratosthenes)\n
      \n
    • If it passes, continue to the third step
    • \n
    • If it fails, return to the first step
    • \n
  4. \n
  5. Perform advanced prime test (Miller-Rabin algorithm)\n
      \n
    • If it passes, output the presumed prime numbers
    • \n
    • If it fails, return to the first step
    • \n
  6. \n
\n

In this software implementation, the first step can generate odd numbers directly. Also for demonstration purposes, the second step uses the first 50 prime numbers greater than 2 for the basic primality test. The whole process is shown in the following flowchart.

\n

\n

For the first step, Python function programming requires importing the library function randrange() from the random library. The function uses the input number of bits n in the exponents of 2, which specify the start and end values of randrange(). It also sets the step size to 2 to ensure that only n-bit random odd values are returned.

\n
from random import randrange

def generate_n_bit_odd(n: int):
'''Generate a random odd number in the range [2**(n-1)+1, 2**n-1]'''
assert n > 1
return randrange(2 ** (n - 1) + 1, 2 ** n, 2)
\n

The code for the second step is simple. It defines an array with elements of 50 prime numbers after 2, then uses a double loop in the function to implement the basic primality test. The inner for loop runs the test with the elements of the prime array one by one. It aborts back to the outer loop immediately upon failure, from there it calls the function in the first step to generate the next candidate odd number and test again.

\n
def get_lowlevel_prime(n):
"""Generate a prime candidate not divisible by first primes"""
while True:
# Obtain a random odd number
c = generate_n_bit_odd(n)

# Test divisibility by pre-generated primes
for divisor in first_50_primes:
if c % divisor == 0 and divisor ** 2 <= c:
break
else:
# The for loop did not encounter a break statement,
# so it passes the low-level primality test.
return c
\n

The Miller-Rabin primality test1 in the third step is a widely used method for testing prime numbers. It uses a probabilistic algorithm to determine whether a given number is a composite or possibly a prime number. Although also based on Fermat's little theorem, the Miller-Rabin primality test is much more efficient than the Fermat primality test. Before showing the Python implementation of the Miller-Rabin prime test, a brief description of how it works is given here.

\n

By Fermat's little theorem, for a prime \\(n\\), if the integer \\(a\\) is not a multiple of \\(n\\), then we have \\(a^{n-1}\\equiv 1\\pmod n\\). Therefore if \\(n>2\\), \\(n-1\\) is an even number and must be expressed in the form \\(2^{s}*d\\), where both \\(s\\) and \\(d\\) are positive integers and \\(d\\) is odd. This yields \\[a^{2^{s}*d}\\equiv 1\\pmod n\\] If we keep taking the square root of the left side of the above equation and then modulo it, we will always get \\(1\\) or \\(-1\\)2. If we get \\(1\\), it means that the following equation ② holds; if we never get \\(1\\), then equation ① holds: \\[a^{d}\\equiv 1{\\pmod {n}}{\\text{ ①}}\\] \\[a^{2^{r}d}\\equiv -1{\\pmod {n}}{\\text{ ②}}\\] where \\(r\\) is some integer that lies in the interval \\([0, s-1]\\). So, if \\(n\\) is a prime number greater than \\(2\\), there must be either ① or ② that holds. The conditional statement of this law is also true, i.e.** if we can find a \\(\\pmb{a}\\) such that for any \\(\\pmb{0\\leq r\\leq s-1}\\) the following two equations are satisfied: \\[\\pmb{a^{d}\\not \\equiv 1\\pmod n}\\] \\[\\pmb{a^{2^{r}d}\\not \\equiv -1\\pmod n}\\] Then \\(\\pmb{n}\\) must not be a prime number**. This is the mathematical concept of the Miller-Rabin primality test. For the number \\(n\\) to be tested, after calculating the values of \\(s\\) and \\(d\\), the base \\(a\\) is chosen randomly and the above two equations are tested iteratively. If neither holds, \\(n\\) is a composite number, otherwise, \\(n\\) may be a prime number. Repeating this process, the probability of \\(n\\) being a true prime gets larger and larger. Calculations show that after \\(k\\) rounds of testing, the maximum error rate of the Miller-Rabin primality test does not exceed \\(4^{-k}\\).

\n

The Miller-Rabin primality test function implemented in Python is as follows, with the variables n,s,d,k in the code corresponding to the above description.

\n
def miller_rabin_primality_check(n, k=20):
'''Miller-Rabin Primality Test with a specified round of test
Input:
n - n > 3, an odd integer to be tested for primality
k - the number of rounds of testing to perform
Output:
True - passed (n is a strong probable prime)
False - failed (n is a composite)'''

# For a given odd integer n > 3, write n as (2^s)*d+1,
# where s and d are positive integers and d is odd.
assert n > 3
if n % 2 == 0:
return False

s, d = 0, n - 1
while d % 2 == 0:
d >>= 1
s += 1

for _ in range(k):
a = randrange(2, n - 1)
x = pow(a, d, n)

if x == 1 or x == n - 1:
continue

for _ in range(s):
x = pow(x, 2, n)
if x == n - 1:
break
else:
# The for loop did not encounter a break statement,
# so it fails the test, it must be a composite
return False

# Passed the test, it is a strong probable prime
return True
\n

Putting all of the above together, the whole process can be wrapped into the following function, where the input of the function is the number of bits and the output is a presumed random large prime number.

\n
def get_random_prime(num_bits):
while True:
pp = get_lowlevel_prime(num_bits)
if miller_rabin_primality_check(pp):
return pp
\n

Utility Functions

\n
    \n
  1. Greatest Common Divisor (GCD) gcd(a,b) and Least Common Multiple lcm(a,b):
    \nThe RSA encryption algorithm needs to calculate the Carmichael function \\(\\lambda(N)\\) of modulus \\(N\\), with the formula \\(\\lambda(pq)= \\operatorname{lcm}(p - 1, q - 1)\\), where the least common multiple function is used. The relationship between the least common multiple and the greatest common divisor is: \\[\\operatorname{lcm}(a,b)={\\frac{(a\\cdot b)}{\\gcd(a,b)}}\\] There is an efficient Euclidean algorithm for finding the greatest common divisor, which is based on the principle that the greatest common divisor of two integers is equal to the greatest common divisor of the smaller number and the remainder of the division of the two numbers. The specific implementation of Euclid's algorithm can be done iteratively or recursively. The iterative implementation of the maximum convention function is applied here, and the Python code for the two functions is as follows:

    \n

    def gcd(a, b):
    '''Computes the Great Common Divisor using the Euclid's algorithm'''
    while b:
    a, b = b, a % b
    return a

    def lcm(a, b):
    """Computes the Lowest Common Multiple using the GCD method."""
    return a // gcd(a, b) * b

  2. \n
  3. Extended Euclidean Algorithm exgcd(a,b) and Modular Multiplicative Inverse invmod(e,m):
    \nThe RSA key pair satisfies the equation \\((d⋅e)\\bmod \\lambda(N)=1\\), i.e., the two are mutually modular multiplicative inverses with respect to the modulus \\(\\lambda(N)\\). The extended Euclidean algorithm can be applied to solve the modular multiplicative inverse \\(d\\) of the public key exponent \\(e\\) quickly. The principle of the algorithm is that given integers \\(a,b\\), it is possible to find integers \\(x,y\\) (one of which is likely to be negative) while finding the greatest common divisor of \\(a,b\\) such that they satisfy Bézout's identity: \\[a⋅x+b⋅y=\\gcd(a, b)\\] substituted into the parameters \\(a=e\\) and \\(b=m=\\lambda(N)\\) of the RSA encryption algorithm, and since \\(e\\) and \\(\\lambda(N)\\) are coprime, we can get: \\[e⋅x+m⋅y=1\\] the solved \\(x\\) is the modulo multiplicative inverse \\(d\\) of \\(e\\). The Python implementations of these two functions are given below:

    \n

    def exgcd(a, b):
    """Extended Euclidean Algorithm that can give back all gcd, s, t
    such that they can make Bézout's identity: gcd(a,b) = a*s + b*t
    Return: (gcd, s, t) as tuple"""
    old_s, s = 1, 0
    old_t, t = 0, 1
    while b:
    q = a // b
    s, old_s = old_s - q * s, s
    t, old_t = old_t - q * t, t
    a, b = b, a % b
    return a, old_s, old_t

    def invmod(e, m):
    """Find out the modular multiplicative inverse x of the input integer
    e with respect to the modulus m. Return the minimum positive x"""
    g, x, y = exgcd(e, m)
    assert g == 1

    # Now we have e*x + m*y = g = 1, so e*x ≡ 1 (mod m).
    # The modular multiplicative inverse of e is x.
    if x < 0:
    x += m
    return x
    Similarly, an iterative approach is applied here to implement the extended Euclidean algorithm, with the modular inverse multiplicative function calling the former.

  4. \n
\n

Implementing RSA Class

\n

Note: Textbook RSA has inherent security vulnerabilities. The reference implementation in the Python language given here is for learning and demonstration purposes only, by no means to be used in actual applications. Otherwise, it may cause serious information security incidents. Keep this in mind!

\n
\n

Based on the object-oriented programming idea, it can be designed to encapsulate the RSA keys and all corresponding operations into a Python class. The decryption and signature generation of the RSA class are each implemented in two ways, regular and fast. The fast method is based on the Chinese Remainder Theorem and Fermat's Little Theorem. The following describes the implementation details of the RSA class.

\n
    \n
  1. Object Initialization Method
    \nInitialization method __init__() has the user-defined paramaters with default values shown as below:

    \n
      \n
    • Key bit-length (\\(N\\)):2048
    • \n
    • Public exponent (\\(e\\)):65537
    • \n
    • Fast decryption or signature generation:False
    • \n
    \n

    This method internally calls the get_random_prime() function to generate two large random prime numbers \\(p\\) and \\(q\\) that are about half the bit-length of the key. It then calculates their Carmichael function and verifies that the result and \\(e\\) are coprime. If not, it repeats the process till found. Thereafter it computes the modulus \\(N\\) and uses the modular multiplicative inverse function invmod() to determine the private exponent \\(d\\). If a fast decryption or signature generation function is required, three additional values are computed as follows: \\[\\begin{align}\nd_P&=d\\bmod (p-1)\\\\\nd_Q&=d\\bmod (q-1)\\\\\nq_{\\text{inv}}&=q^{-1}\\pmod {p}\n\\end{align}\\]

    \n

    RSA_DEFAULT_EXPONENT = 65537
    RSA_DEFAULT_MODULUS_LEN = 2048

    class RSA:
    """Implements the RSA public key encryption/decryption with default
    exponent 65537 and default key size 2048"""

    def __init__(self, key_length=RSA_DEFAULT_MODULUS_LEN,
    exponent=RSA_DEFAULT_EXPONENT, fast_decrypt=False):
    self.e = exponent
    self.fast = fast_decrypt
    t = 0
    p = q = 2

    while gcd(self.e, t) != 1:
    p = get_random_prime(key_length // 2)
    q = get_random_prime(key_length // 2)
    t = lcm(p - 1, q - 1)

    self.n = p * q
    self.d = invmod(self.e, t)

    if (fast_decrypt):
    self.p, self.q = p, q
    self.d_P = self.d % (p - 1)
    self.d_Q = self.d % (q - 1)
    self.q_Inv = invmod(q, p)

  2. \n
  3. Encryption and Decryption Methods
    \nRSA encryption and regular decryption formulas are \\[\\begin{align}\nc\\equiv m^e\\pmod N\\\\\nm\\equiv c^d\\pmod N\n\\end{align}\\] Python built-in pow() function supports modular exponentiation. The above two can be achieved by simply doing the corresponding integer to byte sequence conversions and then calling pow() with the public or private key exponent:

    \n

    def encrypt(self, binary_data: bytes):
    int_data = uint_from_bytes(binary_data)
    return pow(int_data, self.e, self.n)
    \t
    def decrypt(self, encrypted_int_data: int):
    int_data = pow(encrypted_int_data, self.d, self.n)
    return uint_to_bytes(int_data)
    For fast descryption, a few extra steps are needed: \\[\\begin{align}\nm_1&=c^{d_P}\\pmod {p}\\tag{1}\\label{eq1}\\\\\nm_2&=c^{d_Q}\\pmod {q}\\tag{2}\\label{eq2}\\\\\nh&=q_{\\text{inv}}(m_1-m_2)\\pmod {p}\\tag{3}\\label{eq3}\\\\\nm&=m_{2}+hq\\pmod {pq}\\tag{4}\\label{eq4}\n\\end{align}\\] In practice, if \\(m_1-m_2<0\\) in the step \\((3)\\), \\(p\\) needs to be added to adjust to a positive number. It can also be seen that the acceleration ratio would theoretically be close to \\(4\\) because the fast decryption method decreases the modulus and exponent by roughly half the order. Considering the additional computational steps, the actual speedup ratio estimate is subtracted by a correction \\(\\varepsilon\\), noted as \\(4-\\varepsilon\\). The code of the fast decryption function is as follows:

    \n

    def decrypt_fast(self, encrypted_int_data: int):
    # Use Chinese Remaider Theorem + Fermat's Little Theorem to
    # do fast RSA description
    assert self.fast == True
    m1 = pow(encrypted_int_data, self.d_P, self.p)
    m2 = pow(encrypted_int_data, self.d_Q, self.q)
    t = m1 - m2
    if t < 0:
    t += self.p
    h = (self.q_Inv * t) % self.p
    m = (m2 + h * self.q) % self.n
    return uint_to_bytes(m)

  4. \n
  5. Signature Generation and Verification Methods
    \nThe RSA digital signature generation and verification methods are very similar to encryption and regular decryption functions, except that the public and private exponents are used in reverse. The signature generation uses the private exponent, while the verification method uses the public key exponent. The implementation of fast signature generation is the same as the fast decryption steps, but the input and output data are converted and adjusted accordingly. The specific implementations are presented below:

    \n

    def generate_signature(self, encoded_msg_digest: bytes):
    """Use RSA private key to generate Digital Signature for given
    encoded message digest"""
    int_data = uint_from_bytes(encoded_msg_digest)
    return pow(int_data, self.d, self.n)
    \t
    def generate_signature_fast(self, encoded_msg_digest: bytes):
    # Use Chinese Remaider Theorem + Fermat's Little Theorem to
    # do fast RSA signature generation
    assert self.fast == True
    int_data = uint_from_bytes(encoded_msg_digest)
    s1 = pow(int_data, self.d_P, self.p)
    s2 = pow(int_data, self.d_Q, self.q)
    t = s1 - s2
    if t < 0:
    t += self.p
    h = (self.q_Inv * t) % self.p
    s = (s2 + h * self.q) % self.n
    return s

    def verify_signature(self, digital_signature: int):
    """Use RSA public key to decrypt given Digital Signature"""
    int_data = pow(digital_signature, self.e, self.n)
    return uint_to_bytes(int_data)

  6. \n
\n

Functional Tests

\n

Once the RSA class is completed, it is ready for testing. To test the basic encryption and decryption functions, first initialize an RSA object with the following parameters

\n
    \n
  • Key length (modulo \\(N\\)): 512 bits
  • \n
  • Public exponent (\\(e\\)): 3
  • \n
  • Fast decryption or signature generation: True
  • \n
\n

Next, we can call the encryption method encrypt() of the RSA object instance to encrypt the input message, and then feed the ciphertext to the decryption method decrypt() and the fast decryption method decrypt_fast() respectively. We use the assert statement to compare the result with the original message. The code snippet is as follows.

\n
# ---- Test RSA class ----
alice = RSA(512, 3, True)
msg = b'Textbook RSA in Python'
ctxt = alice.encrypt(msg)
assert alice.decrypt(ctxt) == msg
assert alice.decrypt_fast(ctxt) == msg
print("RSA message encryption/decryption test passes!")
\n

Likewise, we can also test the signature methods. In this case, we need to add the following import statement to the beginning of the file

\n
from hashlib import sha1
\n

This allows us to generate the message digest with the library function sha1() and then call the generate_signature() and generate_signature_fast() methods of the RSA object instance to generate the signature, respectively. Both signatures are fed to the verify_signature()` function and the result should be consistent with the original message digest. This test code is shown below.

\n
mdg = sha1(msg).digest()
sign1 = alice.generate_signature(mdg)
sign2 = alice.generate_signature_fast(mdg)

assert alice.verify_signature(sign1) == mdg
assert alice.verify_signature(sign2) == mdg
print("RSA signature generation/verification test passes!")
\n

If no AssertionError is seen, we would get the following output, indicating that both the encryption and signature tests passed.

\n
RSA message encryption/decryption test passes!
RSA signature generation/verification test passes!
\n

Performance Tests

\n

Once the functional tests are passed, it is time to see how the performance of fast decryption is. We are interested in what speedup ratio we can achieve, which requires timing the execution of the code. For time measurements in Python programming, we have to import the functions urandom() and timeit() from the Python built-in libraries os and timeit, respectively:

\n
from os import urandom
from timeit import timeit
\n

urandom() is for generaring random bype sequence, while timeit() can time the execution of a given code segment. For the sake of convenience, the RSA decryption methods to be timed are first packed into two functions:

\n
    \n
  • decrypt_norm() - Regular decryption method
  • \n
  • decrypt_fast() - Fast descryption method
  • \n
\n

Both use the assert statement to check the result, as shown in the code below:

\n
def decrypt_norm(tester, ctxt: bytes, msg: bytes):
ptxt = tester.decrypt(ctxt)
assert ptxt == msg

def decrypt_fast(tester, ctxt: bytes, msg: bytes):
ptxt = tester.decrypt_fast(ctxt)
assert ptxt == msg
\n

The time code sets up two nested for loops:

\n
    \n
  • The outer loop iterates over different key lengths klen, from 512 bits to 4096 bits in 5 levels, and the corresponding RSA object obj is initialized with:

    \n
      \n
    • Key length (modular \\(N\\)): klen
    • \n
    • Public exponent (\\(e\\)): 65537
    • \n
    • Fast decryption or signature generation: True
    • \n
    \n

    The variable rpt is also set in the outer loop to be the square root of the key length, and the timing variables t_n and t_f are cleared to zeros.

  • \n
  • The inner layer also loops 5 times, each time executing the following operations:

    \n
      \n
    • Call urandom() to generate a random sequence of bytes mg with bits half the length of the key
    • \n
    • Call obj.encrypt() to generate the ciphertext ct
    • \n
    • call timeit() and enter the packing functions decrypt_norm() and decrypt_fast() with the decryption-related parameters obj, ct and mg, respectively, and set the number of executions to rpt
    • \n
    • The return values of the timeit() function are stored cumulatively in t_n and t_f
    • \n
  • \n
\n

At the end of each inner loop, the current key length, the mean value of the timing statistics, and the calculated speedup ratio t_n/t_f are printed. The actual program segment is printed below:

\n
print("Start RSA fast decryption profiling...")
for klen in [512, 1024, 2048, 3072, 4096]:
rpt = int(klen ** 0.5)
obj = RSA(klen, 65537, True)
t_n = t_f = 0
for _ in range(5):
mg = urandom(int(klen/16))
ct = obj.encrypt(mg)
t_n += timeit(lambda: decrypt_norm(obj, ct, mg), number=rpt)
t_f += timeit(lambda: decrypt_fast(obj, ct, mg), number=rpt)
print("Key size %4d => norm %.4fs, fast %.4fs\\tSpeedup: %.2f"
% (klen, t_n/5/rpt, t_f/5/rpt, t_n/t_f))
\n

Here are the results on a Macbook Pro laptop:

\n
Start RSA fast decryption profiling...
Key size 512 => norm 0.0008s, fast 0.0003s Speedup: 2.43
Key size 1024 => norm 0.0043s, fast 0.0015s Speedup: 2.88
Key size 2048 => norm 0.0273s, fast 0.0085s Speedup: 3.19
Key size 3072 => norm 0.0835s, fast 0.0240s Speedup: 3.48
Key size 4096 => norm 0.1919s, fast 0.0543s Speedup: 3.53
\n

The test results confirm the effectiveness of the fast decryption method. As the key length increases, the computational intensity gradually increases and the running timeshare of the core decryption operation becomes more prominent, so the speedup ratio grows correspondingly. However, the final speedup ratio tends to a stable value of about 3.5, which is consistent with the upper bound of the theoretical estimate (\\(4-\\varepsilon\\)).

\n

The Python code implementation of the textbook RSA helps reinforce the basic number theory knowledge we have learned and also benefits us with an in-depth understanding of the RSA encryption algorithm. On this basis, we can also extend to experiment some RSA elementary attack and defense techniques to further master this key technology of public-key cryptography. For the complete program click here to download: textbook-rsa.py.gz

\n
\n
\n
    \n
  1. Gary Lee Miller, a professor of computer science at Carnegie Mellon University, first proposed a deterministic algorithm based on the unproven generalized Riemann hypothesis. Later Professor Michael O. Rabin of the Hebrew University of Jerusalem, Israel, modified it to obtain an unconditional probabilistic algorithm.↩︎

  2. \n
  3. This is because it follows from \\(x^2\\equiv 1\\pmod n\\) that \\((x-1)(x+1)=x^{2}-1\\equiv 0\\pmod n\\). Since \\(n\\) is a prime number, by Euclid's Lemma, it must divide either \\(x- 1\\) or \\(x+1\\), so \\(x\\bmod n\\) must be \\(1\\) or \\(-1\\).↩︎

  4. \n
\n
\n","categories":["Technical Know-how"],"tags":["Cryptography","Python Programming"]},{"title":"Build an Awesome Raspberry Pi NAS for Home Media Streaming","url":"/en/2021/12/29/RPi-NAS-Plex/","content":"

Network Attached Storage (NAS) provides data access to a heterogeneous group of clients over computer networks. As hard drive prices continue to drop, NAS devices have made their way into the homes of the masses. Leading brands in the SMB and home NAS market, such as Synology, have their products range in price from as low as ﹩300 to ﹩700 for the high models. But if you are a Raspberry Pi player, you can build a very nice home NAS and streaming service for only about half the cost of the lowest price.

\n

Knowledge obtained on the papers always feels shallow, must know this thing to practice.
LU You (Chinese historian and poet of the Southern Song Dynasty)

\n
\n

This blog records the whole process of building a Raspberry Pi NAS and home media server, including project planning, system implementation, and performance review. It also covers some important experiences and lessons that could hopefully benefit anyone interested in this DIY project.

\n

Project Planning

\n

Raspberry Pi 4B features an upgraded 1.8GHz Broadcom BCM2711(quad-core Cortex-A72)processor and onboard RAM up to 8GB. It includes two new USB 3.0 ports and a full-speed Gigabit Ethernet interface. The power supply is also updated to a USB-C connector. All these greatly improve system throughput and overall comprehensive performance, and we can use them to create a full-featured home NAS.

\n

For NAS system software, OpenMediaVault (OMV) is a complete NAS solution based on Debian Linux. It is a Linux rewrite of the well-known free and open-source NAS server system FreeNAS (based on FreeBSD). The salient features of OMV are

\n
    \n
  • Simple and easy-to-use out-of-the-box solution, no need for expert-level knowledge of computer networking and storage systems
  • \n
  • Available for x86-64 and ARM platforms with a full Web Administration interface
  • \n
  • Supports a variety of different protocols (SFTP、SMB/CIFS, NFS, etc.) for file storage access
  • \n
  • Can be controlled via SSH (if enabled), and provides Access Right Management for users and groups
  • \n
\n

While primarily designed for home environments or small home offices, OMV's use is not limited to those scenarios. The system is built on a modular design. It can be easily extended with available plugins right after the installation of the base system. OMV is the NAS server system software we are looking for.

\n

The NAS system with media playback services provides an excellent audio/video-on-demand experience in a home network environment. Plex Media Server software integrates Internet media services (YouTube, Vimeo, TED, etc.) and local multimedia libraries to provide streaming media playback on users' various devices. The features of Plex for managing local libraries are

\n
    \n
  • Centralized management and easy sharing of a single library
  • \n
  • Web interface with media resource navigation, streaming playback
  • \n
  • Real-time saving and resuming of playback progress
  • \n
  • Multi-user support and hierarchical playback rights settings
  • \n
\n

The Plex Media Server software itself is free and supports a wide range of operating systems, making it ideal for integration with home NAS.

\n

These cover all the software needed for our NAS project, but they are not enough for a complete NAS system. We also need a preferred case, otherwise, the Raspberry Pi NAS will only run bare metal. Although there are many cases available in the market for Raspberry Pi 4B, as a NAS system we need a case kit that can accommodate at least 1-2 internal SSD/HDD and must also have a good heat dissipation design.

\n

After some review and comparison, we chose Geekworm's NASPi Raspberry Pi 4B NAS storage kit. NASPi is a NUC (Next Unit of Computing) style NAS storage kit designed for the latest Raspberry Pi 4B. It consists of three components:

\n
    \n
  1. X823 shield board, which provides storage function for 2.5-inch SDD/HDD
  2. \n
  3. X-C1 adapter board, which adjusts all Raspberry Pi 4B interfaces to the back of the case and provides power management and safe shutdown function
  4. \n
  5. Temperature-controlled PWM (Pulse-Width Modulation) fan as the cooling system
  6. \n
\n

All these components are packed into a case made of aluminum alloy with an anodized surface.

\n

Thereon our NAS project can be planned with the following subsystems:

\n
    \n
  • Hardware System:\n
      \n
    • Raspberry Pi 4B 8GB RAM
    • \n
    • 32GB microSD for OS storage
    • \n
    • NASPi NAS storage kit
    • \n
    • 15-20W USB-C power adaptor
    • \n
    • 500GB internal SSD(USB 3.0)
    • \n
    • 2TB external HDD(USB 3.0)
    • \n
  • \n
  • Software System:\n
      \n
    • Raspberry Pi OS Lite(with no desktop environment)
    • \n
    • OMV for NAS file server
    • \n
    • Plex media server providing streaming service
    • \n
  • \n
\n

It is important to note that NAS servers are generally headless systems without a keyboard, mouse, or monitor. This poses some challenges for the installation, configuration, and tuning of hardware and software systems. In practice, as described in the next section, we run an SSH terminal connection to complete the basic project implementation process.

\n

System Implementation

\n

The execution of this project was divided into four stages, which are described in detail as follows.

\n

Prepare Raspberry Pi 4B

\n

In the first stage, we need to prepare the Raspberry Pi OS and do some basic unit tests. This is important, if we delay the OS test until the entire NSAPi kit is assembled, it will be troublesome to find problems with the Raspberry Pi then.

\n

Bake Raspberry Pi OS

\n

First, insert the microSD card into the USB adapter and connect it to the macOS computer, then go to the Raspberry Pi website and download the Raspberry Pi Imager software to run. From the application screen, click CHOOSE OS > Raspberry Pi OS (other) > Raspberry Pi OS Lite (32-bit) step by step. This selects the lightweight Raspberry Pi OS that does not require a desktop environment, and then click CHOOSE STORAGE to pick the microSD card.

\n

Next is a trick - hit the ctrl-shift-x key combination and the following advanced options dialog box will pop up Here is exactly the option we need to enable SSH on boot up - Enable SSH. It also allows the user to pre-set a password for the default username pi (default is raspberry). Once set up, click SAVE to return to the main page and then click WRITE to start formatting the microSD card and writing OS to it. When finished, remove the microSD card and insert it into the Raspberry Pi, connect the Ethernet cable then power it up.

\n

Probe IP Address

\n

At this point we encountered a problem: since the installed system does not have a desktop environment, it cannot connect to the keyboard, mouse, and monitor, so how do we find its IP address? There are two ways:

\n
    \n
  1. connect to the home router's management WebUI and find the address for the hostname 'raspberry'.
  2. \n
  3. run the Nmap tool to scan the target subnet and check the changes before and after the Raspberry Pi boots up
  4. \n
\n

The log of the Nmap tool run can be seen below. Notice that a new IP address 192.168.2.4 is showing up in the scan report. Rerunning Nmap against this address alone, we saw that TCP port 22 was open. We could roughly determine that this might be our newly online Raspberry Pi:

\n
❯ nmap -sn 192.168.2.0/24
Starting Nmap 7.92 ( https://nmap.org ) at 2021-11-28 21:07 PST
Nmap scan report for router.sx.com (192.168.2.1)
Host is up (0.0050s latency).
Nmap scan report for 192.168.2.3
Host is up (0.0048s latency).
Nmap scan report for 192.168.2.4 ## New IP after Raspberry Pi boots up
Host is up (0.0057s latency).
Nmap done: 256 IP addresses (3 hosts up) scanned in 15.31 seconds

❯ nmap 192.168.2.4
Nmap scan report for 192.168.2.4
Host is up (0.0066s latency).
Not shown: 999 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
\n

System Update and Upgrade

\n

Next, try SSH connection

\n
❯ ssh pi@192.168.2.4
pi@192.168.2.4's password:
Linux raspberrypi 5.10.63-v7l+ #1488 SMP Thu Nov 18 16:15:28 GMT 2021 armv7l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Dec 24 19:46:15 2021 from 192.168.2.3
pi@raspberrypi:~ $
\n

Once confirmed, we executed the following commands in the Raspberry Pi to update and upgrade the system:

\n
pi@raspberrypi:~ $ sudo apt update && sudo apt upgrade
\n

Network Connectivity Test

\n

This stage concluded with the stability test of the Raspberry Pi 4B system Ethernet connection. The test was executed on a macOS computer using the simple ping command, setting the -i 0.05 option to specify 20 packets per second and the -t 3600 option for one hour run

\n
❯ sudo ping -i 0.05 192.168.2.4 -t 3600
\n

There should be no more than 1% packet loss or timeout on a subnet with no wireless connectivity, otherwise, it should be checked for troubleshooting. As a matter of fact, in our test, it was happening that nearly 10% of ping packets got lost and the SSH connection dropped intermittently. Searching the Internet, we found that there have been quite a few reports of similar issues with the Raspberry Pi 4B Ethernet connection. The analysis and suggestions given by people on the relevant forums focus on the following

\n
    \n
  1. Unstable power supply accounts for packet loss, and needs to be replaced with a reliable USB-C power adapter of 15W or more.
  2. \n
  3. Energy-efficient Ethernet (Energy-Efficient Ethernet) malfunction, can be fixed by disabling it.
  4. \n
  5. The full-speed Gigabit Ethernet connection function is faulty and has to be downgraded to 100Mbit/s for stable use.
  6. \n
\n

Practically, we tried all of the above with little success. Later, we found that the home router connected to the Raspberry Pi 4B was a Belkin N750 DB made in 2011. Although it provides Wi-Fi dual-band 802.11n and 4 Gigabit Ethernet ports, the manufacturing date is too long ago, which makes people doubt its interoperability. Also points 2 and 3 of the above report are essentially interoperability issues. Thinking of these, we immediately ordered the TP-Link TL-SG105 5-port Gigabit Ethernet switch. After receiving it, we extended the Gigabit Ethernet port of N750 with TL-SG105, connected Raspberry Pi 4B to TL-SG105, and retested it. Sure enough, this time the ping packet loss rate was less than 0.1% and the SSH connection became solid.

\n

The conclusion is that the Raspberry Pi 4B Gigabit Ethernet interface may have compatibility issues with some older devices, which can be solved by inserting a desktop switch with good interoperability between the two.

\n

NSAPi Kit Assembly

\n

In the second stage, we assembled the NSAPi storage kit, intending to finish all hardware installation and complete the standalone NAS body.

\n

Prepare Internal SSD

\n

The NSAPi supports either an internal SSD or HDD. The project picked a Samsung 870 EVO 500GB internal SSD, here we ought to first make sure the SSD works properly on its own, otherwise, we would have to disassemble the NASPi to replace it. The SSD can be hooked up to Windows for file systems and basic read/write operation checks. In the case of a newly purchased SSD, the following steps can be done on Windows to quickly format it:

\n
    \n
  1. Click on Start or the Windows button, select Control Panel > System and Security
  2. \n
  3. Select Administrative Tools > Computer Management > Disk management
  4. \n
  5. Choose the disk to be formatted, right-click then select Format
  6. \n
  7. Check the following in the Dialog box pop-up\n
      \n
    • File System → NTFS
    • \n
    • Allocation Unit Size → Default
    • \n
    • Volume Label → (enter volume name)
    • \n
    • Perform a quick format
    • \n
  8. \n
  9. Click the OK button to start a fast format for the SSD
  10. \n
\n

⚠️Note: Here the chosen file system is NTFS. OMV supports NTFS mounting and reads/writes.

\n

PWM Fan Control

\n

Before the actual hardware assembly, a special software provided by Geekworm - PWM fan control script - must be installed. PWM fan speed adjustment to temperature change is a major feature that lets NASPi stand out from other hardware solutions. So this step is critical.

\n

Referring to Geekworm's X-C1 software wiki page, the installation command sequence on the SSH session connected to the Raspberry Pi 4B system is as follows

\n
sudo apt-get install -y git pigpio 
sudo apt-get install -y python3-pigpio
sudo apt-get install -y python3-smbus
git clone https://github.com/geekworm-com/x-c1.git
cd x-c1
sudo chmod +x *.sh
sudo bash install.sh
echo "alias xoff='sudo /usr/local/bin/x-c1-softsd.sh'" >> ~/.bashrc
\n

If you can't do git clone directly on Raspberry Pi 4B, you can first download the X-C1 software on the SSH client, then transfer it to Raspberry Pi 4B using scp. After that, continue to execute the subsequent commands

\n
❯ scp -r x-c1 pi@192.168.2.4:/home/pi/
\n

How does X-C1 software control PWM fan?

\n
\n

The core of X-C1 software is a Python script named fan.py, which is presented below

\n
#!/usr/bin/python
import pigpio
import time

servo = 18

pwm = pigpio.pi()
pwm.set_mode(servo, pigpio.OUTPUT)
pwm.set_PWM_frequency( servo, 25000 )
pwm.set_PWM_range(servo, 100)
while(1):
#get CPU temp
file = open("/sys/class/thermal/thermal_zone0/temp")
temp = float(file.read()) / 1000.00
temp = float('%.2f' % temp)
file.close()

if(temp > 30):
pwm.set_PWM_dutycycle(servo, 40)

if(temp > 50):
pwm.set_PWM_dutycycle(servo, 50)

if(temp > 60):
pwm.set_PWM_dutycycle(servo, 70)

if(temp > 70):
pwm.set_PWM_dutycycle(servo, 80)

if(temp > 75):
pwm.set_PWM_dutycycle(servo, 100)

if(temp < 30):
pwm.set_PWM_dutycycle(servo, 0)
time.sleep(1)
\n

Its logic is quite simple. With the pigpio module imported, it first initializes a PWM control object and then starts a while loop with a 1-second sleep cycle inside. The CPU temperature is read at each cycle, and the duty cycle of PWM is set according to the temperature level to control the fan speed. The duty cycle is 0 when it is lower than 30℃, and the fan stops; when it is higher than 75℃, the duty cycle is 100, and the fan spins at full speed. Users can modify the temperature threshold and duty cycle parameters in the program to customize the PWM fan control.

\n\n
\n

In addition, the following pi-temp.sh script, which reads out the GPU and CPU temperatures, is also useful

\n
pi@raspberrypi:~ $ cat ./pi-temp.sh
#!/bin/bash
# Script: pi-temp.sh
# Purpose: Display the ARM CPU and GPU temperature of Raspberry Pi
# -------------------------------------------------------
cpu=$(</sys/class/thermal/thermal_zone0/temp)
echo "$(date) @ $(hostname)"
echo "-------------------------------------------"
echo "GPU => $(vcgencmd measure_temp)"
echo "CPU => temp=$((cpu/1000))’C"

pi@raspberrypi:~ $ ./pi-temp.sh
Mon 29 Nov 06:59:17 GMT 2021 @ raspberrypi
-------------------------------------------
GPU => temp=33.1'C
CPU => temp=32’C
\n

Hardware Assembly Process

\n

Below is a snapshot of the Geekworm NASPi parts out of the box (except for the Raspberry Pi 4B on the far right of the second row and the screwdriver in the lower right corner)

\n

The three key components in the second row, from left to right, are

\n
    \n
  • X-C1 V1.3 adapter board provides power management, interface adaptation, and security shutdown functions
  • \n
  • X823 V1.5 shield board provides a 2.5-inch SSD/HDD storage function (UASP supported)
  • \n
  • 4010 PWM fan and metal fan bracket
  • \n
\n

The assembly process was done step-by-step mainly by referring to NASPi installation video on Youtube, and the steps are generalized as follows.

\n
    \n
  1. Insert the SSD into the SATA III connector of X823, flip it to the other side, and fix it with screws.
  2. \n
  3. Install the Raspberry Pi 4B after fixing the spacers on this side, and place the 7-pin cable between the two
  4. \n
  5. Install the PWM fan on top of the Raspberry Pi 4B with the additional spacers
  6. \n
  7. Connect X-C1 and Raspberry Pi 4B, insert a 7-pin connector right to the X-C1 GPIO port and a 3-pin connector to the X-C1 FAN port
  8. \n
  9. Align and insert the 2x7-pin daughterboard to the GPIO port of the Raspberry Pi 4B and fix it with screws
  10. \n
  11. Plug in the USB 3.0 connector to connect the X823 USB 3.0 port to the corresponding Raspberry Pi 4B USB 3.0
  12. \n
\n

Now the installation of the internal accessories has been completed, we have a view of this

\n

\n

At this point, we added the USB-C power and pressed the front button to start the system, we could see the PWM fan started to spin. It was also observed that the fan spin rate was not constant, which demonstrated that the temperature controller PWM fan was working properly.

\n

The front button switch with embedded blue LED decides the whole system's on/off state and can be tested below

\n
    \n
  • Press the switch after power-on, and the system starts
  • \n
  • Press and hold the switch for 1-2 seconds while running, then the system restarts
  • \n
  • Press and hold the switch for 3 seconds during operation to shut down the system safely.
  • \n
  • Press and hold the switch for 7-8 seconds during operation to force shutdown
  • \n
\n

Running the off command on the SSH connection can also trigger a safe shutdown. Be cautious that we should not use the Linux shutdown command, as that would not power down the X-C1 board.

\n

After the button switch test, we now unplugged the USB 3.0 connector and inserted the entire module into the case. Next was to add the back panel and tighten the screws, then re-insert the USB 3.0 connector. This completed the whole NASPi storage kit assembly process. Below are the front and rear views of the final system provided by Geekworm (all interfaces and vents are marked).

\n

\n

OMV Installation and Configuration

\n

The third stage is for installing and configuring the key software package of the NAS system - PMV. The goal is to bring up the basic network file access service. Before restarting the NAS, we plugged a Seagate 2TB external HDD into the remaining USB 3.0 port. After booting, connected SSH to NASPi from macOS and performed the following process.

\n

Install OMV Package

\n

Installing OMV is as simple as running the following command line directly from a terminal with an SSH connection.

\n
wget -O - https://raw.githubusercontent.com/OpenMediaVault-Plugin-Developers/installScript/master/install | sudo bash
\n

Due to the large size of the entire OMV package, this installation process can take a long time. After the installation, the IP address of the system may change and you will need to reconnect to SSH at this time.

\n
(Reading database ... 51781 files and directories currently installed.)
Purging configuration files for dhcpcd5 (1:8.1.2-1+rpt3) ...
Purging configuration files for raspberrypi-net-mods (1.3.2) ...
Enable and start systemd-resolved ...
Unblocking wifi with rfkill ...
Adding eth0 to openmedivault database ...
IP address may change and you could lose connection if running this script via ssh.
client_loop: send disconnect: Broken pipe\t
\n

After reconnecting, you can use dpkg to view the OMV packages. As you can see, the latest version of OMV installed here is 6.0.5.

\n
pi@raspberrypi:~ $ dpkg -l | grep openme
ii openmediavault 6.0.5-1 all openmediavault - The open network attached storage solution
ii openmediavault-flashmemory 6.0.2 all folder2ram plugin for openmediavault
ii openmediavault-keyring 1.0 all GnuPG archive keys of the OpenMediaVault archive
ii openmediavault-omvextrasorg 6.0.4 all OMV-Extras.org Package Repositories for OpenMediaVault
\n

OMV Management UI

\n

At this point OMV's workbench is live. Launching a browser on a macOS computer and typing in the IP address will open the beautiful login screen (click on the 🌍 icon in the upper right corner to select the user interface language): After logging in with the default username and password shown above, you will see the Workbench screen. The first thing you should do at this point is to click the ⚙️ icon in the top right corner to bring up the settings menu and click \"Change Password\". You can also change the language here Clicking on \"Dashboard\" in the settings menu allows you to select the relevant components to be enabled. The menu on the left side provides task navigation for administrators and can be hidden when not needed. The complete OMV administration manual can be found in the online documentation

\n

Configure File Services

\n

Next is the key process for configuring the NAS, which consists of the following 5 steps.

\n
    \n
  1. Scan for mounted disk drives

    \n

    Click Storage > Disks from the sidebar menu to enter the hard drive management page. If there is an external USB storage device just plugged in, you can click 🔍 here to scan it out. The scan results for this system are as follows. The internal Samsung 500GB SSD and external Seagate 2TB HDD are detected, and the 32GB microSD that contains the entire software system is listed at the top:

    \n

    On the SSH terminal, we could see the information for the same set of mounted drivers

    \n

    pi@raspberrypi:~ $ df -h | grep disk
    /dev/sdb2 466G 13G 454G 3% /srv/dev-disk-by-uuid-D0604B68604B547E
    /dev/sda1 1.9T 131G 1.7T 7% /srv/dev-disk-by-uuid-DEB2474FB2472B7B

  2. \n
  3. Mount disk drive file systems

    \n

    Click Storage > File Systems from the sidebar menu to enter the file system management page. If the storage device does not have a file system yet, click ⨁ to Create or Mount the file system. OMV can create/mount ext4, ext3, JFS, and xfs file systems, but only mounts are supported for the NTFS file system. The following figure shows that OMV correctly mounts NTFS file systems for SSDs and HDDs:

  4. \n
  5. Set Shared Folders

    \n

    From the sidebar menu, click Storage > File Systems to access the shared folder management page. Here, click ⨁ to create a shared folder. When creating it, specify the name, corresponding file system, and relative path, and you can also add comments. Select the created folder and click the pencil icon again to edit the related information. This system sets the relative paths of shared folders Zixi-Primary and Zixi-Secondary for SSD and HDD respectively Notice the orange alert at the top of the figure above, which alerts the administrator that the configurations have changed and must click on the ✔️ icon to take effect.

  6. \n
  7. Add shared folder access users

    \n

    Click User Management > Users from the sidebar menu to enter the user management page. The system's default user pi has root privileges and cannot be used for file-sharing access due to security concerns. So you need to add a new user separately. On this page, click ⨁ to Create or Import user, only user name and password are required when creating a new user, others are optional. Once created, select this user and click the third folder+key icon (prompting \"Shared folder privileges\") to enter the following privileges settings page As shown in the figure, for this new user zixi, the administrator can set the read and write access permissions for each shared folder.

  8. \n
  9. Start file share services

    \n

    If you expand the \"Services\" item in the navigation menu, you can see that OMV manages five services: FTP, NFS, Rsync, SMB/CIFS, and SSH. SSH is enabled at the beginning of the system OS image preparation. NFS and SMB/CIFS are the most common network file-sharing protocols, and both are supported by macOS. Take SMB/CIFS as an example here. Click Services > SMB/CIFS from the sidebar menu to enter the management page. The page contains two buttons: Settings and Shares. Click \"Settings\" first to activate the SMB/CIFS service and configure the workgroup name on the new page, other options can be left as default. After saving, it returns to the SMB/CIFS administration page. Then enter \"Shares\", click ⨁ to Create shared folders Zixi-Primary and Zixi-Secondary on the new page then save. After that, click the ✔️ icon in the orange warning bar to make all configuration updates take effect, and you will end up with the following result

  10. \n
\n

Now our Raspberry Pi NAS system is ready for file sharing and the SMB/CIFS service is started. After checking the relevant components to turn on, our dashboard live monitoring looks like this

\n

Set Up Client Device

\n

Once the server side is ready, we need to add the network share folder on the client side as follows.

\n
    \n
  • Windows PC client\n
      \n
    • Open File Explore, click “This PC”
    • \n
    • Right-click on the blank area at the right pane, select \"Add a network location” on the popup menu
    • \n
    • Enter “\\\\<IP-address>\\” in the “Internet or network address\" input box
    • \n
    • Enter username and password when prompted
    • \n
  • \n
  • MacBook client (screenshot below)\n
      \n
    • Open Finder, click the menu item Go
    • \n
    • Click “Connect to Server...”
    • \n
    • Enter URL “smb://<IP-address>/”, then click Connect
    • \n
    • Enter username and password when prompted
      \n
    • \n
  • \n
\n

Once the client side is set up, users can perform various operations on the network share folder as if it were a local directory, such as previewing, creating new, opening or copying files, creating new subdirectories, or deleting existing subdirectories.

\n

Plex Installation and Configuration

\n

The last stage is to install and configure the Plex Media Server, and then start a network streaming service.

\n

Install Media Server

\n

The process of installing Plex Media Server requires HTTPS transport support, so we must first install the https-transport package. SSH to our Raspberry Pi NAS and execute the install command

\n
sudo apt-get install apt-transport-https
\n

Next add the Plex repository to the system, which requires downloading the Plex sign key first. Here are the related commands and run logs

\n
pi@raspberrypi:~ $ curl https://downloads.plex.tv/plex-keys/PlexSign.key | sudo apt-key add -
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).
100 3072 100 3072 0 0 10039 0 --:--:-- --:--:-- --:--:-- 10039
OK
\n

Use the same apt-key command to check the newly added Plex sign key

\n
pi@raspberrypi:~ $ apt-key list
Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).
/etc/apt/trusted.gpg
...
pub rsa4096 2015-03-22 [SC]
CD66 5CBA 0E2F 88B7 373F 7CB9 9720 3C7B 3ADC A79D
uid [ unknown] Plex Inc.
sub rsa4096 2015-03-22 [E]
...
\n

You can see that Plex uses 4096-bit RSA keys. For the warning message \"apt-key is deprecated...\" in the above log, you can ignore it for now. Go to read some discussion on the askubuntu forum if you are interested.

\n

The next step is to add the Plex repository to the system repository list, and then update the packages

echo deb https://downloads.plex.tv/repo/deb public main | sudo tee /etc/apt/sources.list.d/plexmediaserver.list
sudo apt-get update
Now we can start the actual Plex Media Server installation with the following installation commands

\n
pi@raspberrypi:~ $ sudo apt install plexmediaserver
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
plexmediaserver
0 upgraded, 1 newly installed, 0 to remove and 20 not upgraded.
Need to get 66.1 MB of archives.
After this operation, 146 MB of additional disk space will be used.
Get:1 https://downloads.plex.tv/repo/deb public/main armhf plexmediaserver armhf 1.25.0.5282-2edd3c44d [66.1 MB]
Fetched 66.1 MB in 28s (2392 kB/s)
Selecting previously unselected package plexmediaserver.
(Reading database ... 51783 files and directories currently installed.)
Preparing to unpack .../plexmediaserver_1.25.0.5282-2edd3c44d_armhf.deb ...
PlexMediaServer install: Pre-installation Validation.
PlexMediaServer install: Pre-installation Validation complete.
Unpacking plexmediaserver (1.25.0.5282-2edd3c44d) ...
Setting up plexmediaserver (1.25.0.5282-2edd3c44d) ...

Configuration file '/etc/apt/sources.list.d/plexmediaserver.list'
==> File on system created by you or by a script.
==> File also in package provided by package maintainer.
What would you like to do about it ? Your options are:
Y or I : install the package maintainer's version
N or O : keep your currently-installed version
D : show the differences between the versions
Z : start a shell to examine the situation
The default action is to keep your current version.
*** plexmediaserver.list (Y/I/N/O/D/Z) [default=N] ?
PlexMediaServer install: PlexMediaServer-1.25.0.5282-2edd3c44d - Installation starting.
PlexMediaServer install:
PlexMediaServer install: Now installing based on:
PlexMediaServer install: Installation Type: New
PlexMediaServer install: Process Control: systemd
PlexMediaServer install: Plex User: plex
PlexMediaServer install: Plex Group: plex
PlexMediaServer install: Video Group: video
PlexMediaServer install: Metadata Dir: /var/lib/plexmediaserver/Library/Application Support
PlexMediaServer install: Temp Directory: /tmp
PlexMediaServer install: Lang Encoding: en_US.UTF-8
PlexMediaServer install: Nvidia GPU card: Not Found
PlexMediaServer install:
PlexMediaServer install: Completing final configuration.
Created symlink /etc/systemd/system/multi-user.target.wants/plexmediaserver.service → /lib/systemd/system/plexmediaserver.service.
PlexMediaServer install: PlexMediaServer-1.25.0.5282-2edd3c44d - Installation successful. Errors: 0, Warnings: 0
\n

The log shows a question is asked about the Plex media server list (plexmediaserver.list), just choose the default N. When we see \"Installation successful\", we know that the installation was successful. At this point, the Plex streaming service is up and running. Invoking the Nmap scan again from the macOS side, we find that TCP port 32400 for Plex service is open.

\n
❯ nmap -p1-65535 192.168.2.4 | grep open
22/tcp open ssh
80/tcp open http
111/tcp open rpcbind
139/tcp open netbios-ssn
445/tcp open microsoft-ds
2049/tcp open nfs
5357/tcp open wsdapi
32400/tcp open plex
\n

Configure Media Server

\n

The configuration of the Plex Media Server has been done on the web GUI. Launch a browser on the macOS computer and type in the URL http://<IP-address>:32400/web, now we can see the following page if no surprise We can sign in with a Google, Facebook, or Apple account, or we can enter an email to create a new account. Follow the instructions on the page step by step, no need for any payment, soon we reach the Server Setup page. Here we can configure the server name and add libraries. Normally we don't need to access our home media server from outside, so remember to uncheck the \"Allow me to access my media outside my home\" box in this step. To add a library, first select the type of library (movies, TV episodes, music, photos, etc.), then click the \"BROWSE FOR MEDIA FOLDER\" button to browse and select the corresponding folder. Once the library is added, the included media files will immediately appear in the local service directory, as shown in the screenshot below Here we have a local server named ZIXI-RPI-NAS for our Raspberry Pi NAS, the movie directory in the library shows The Matrix trilogy and is playing the first one The Matrix. Move your mouse over the server name and ➕ icon will appear to the right, click on it to continue adding new media libraries.

\n

Once the Plex Media Server is configured, we can open a browser from any device on our home network to do streaming on-demand, without the need to download additional applications. The whole experience is just like our own proprietary home Netflix service. This is awesome!

\n

Performance Review

\n

By connecting a macOS laptop to one of the remaining ports of the TL-SG105, we could perform some simple same-subnet tests to evaluate the performance of this NAS system fully.

\n

System Stress Test

\n

Referring to Geekworm NASPi Stress Test Wiki page, we executed the following command over SSH connection, which cloned the test script from GitHub and ran the stress test:

\n
git clone https://github.com/geekworm-com/rpi-cpu-stress
cd rpi-cpu-stress
chmod +x stress.sh
sudo ./stress.sh
\n

Simultaneously we established a second SSH session and ran htop to monitor system status. The screenshot below was taken while close to the 5-minute mark (left is the htop real-time display, and right is the stress test output) Dividing the temp value on the right side by 1000 gave the CPU temperature. All 4 CPU cores reached 100% full load during the test, while the maximum temperature did not exceed 70°C. At this moment, there was no obvious heat sensation when touching the case. Typing ctrl-c to stop the stress test, and then executing the temperature measurement script again

\n
pi@raspberrypi:~ $ ./pi-temp.sh
Fri Dec 24 15:59:21 PST 2021 @ raspberrypi
-------------------------------------------
GPU => temp=39.9'C
CPU => temp=40'C
\n

The system temperature returned to a low range value. This test result assures the system meets the design goal.

\n

File Transfer Speed Test

\n

The file transfer speed can be roughly measured with the secure remote copy tool SCP. First, create a 1GB size file by running the mkfile command on the macOS client, then copy it to the user directory of the remote NAS system with the scp command

\n
❯ mkfile 1G test-nas.dmg
ls -al test-nas.dmg
rw------- 1 sxiao staff 1073741824 Dec 19 20:53 test-nas.dmg
❯ scp test-nas.dmg pi@192.168.2.4:/home/pi/
pi@192.168.2.4's password:
test-nas.dmg 100% 1024MB 19.2MB/s 00:53
\n

After the copy was done, it would print the time spent and the deduced speed. Running the command with the source and the destination reversed would give us the speed of receiving a file from the NAS system.

\n
❯ scp pi@192.168.2.4:/home/pi/test-nas.dmg test-nas-rx.dmg
pi@192.168.2.4's password:
test-nas.dmg 100% 1024MB 65.7MB/s 00:15
\n

Repeated 3 times and got the results listed below

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Transfor TypeServer OperationTime (s)Speed (MB/s)
SendWrite5319.2
SendWrite4522.5
SendWrite5020.4
ReceiveRead1565.7
ReceiveRead1660.3
ReceiveRead1566.3
\n

As can be seen, the speed of remote write is around 20MB/s, while the speed of remote file read can reach over 60MB/s. Considering that scp-related encryption and decryption are implemented in software on general-purpose Raspberry Pi systems, this result should be considered passable.

\n

Disk Access Speed Test

\n

The real test of the NAS's performance is the network drive read/write speed test. For this, we downloaded the AmorphousDiskMark app from Apple's App Store. This is an easy and efficient drive speed test that measures the read/write performance of a storage device in terms of MB/s and IOPS (input/output operations per second). It has four types of tests:

\n
    \n
  1. sequential read/write, 1MB block, queue depth 8
  2. \n
  3. sequential read/write, 1MB block, queue depth 1
  4. \n
  5. random read/write, 4KB block, queue depth 64
  6. \n
  7. random read/write, 4KB block, queue depth 1
  8. \n
\n

The above queue depths are the default values, but other values are also available. In addition, users can also modify the test file size and duration.

\n

Run the application on the macOS client and select the remote SMB folders Zixi-Primary (Samsung SSD) and Zixi-Secondary (Seagate HDD) respectively at the top, then click the All button in the upper left corner to start the NAS drive speed test process. A side-by-side comparison of the two test results is shown below

\n
\n

This gives a few observations:

\n
    \n
  • Reads are faster than writes for NAS drives, and the difference under random access is huge.
  • \n
  • SSD outperforms HDD for both sequential and random accesses.
  • \n
  • Large queue depth speeds up reads, especially for random accesses, but there is little impact on writes.
  • \n
  • For both SSDs and HDDs, sequential reads/writes are significantly more efficient than random reads/writes.
  • \n
  • For both SSDs and HDDs, sequential reads/writes reach their highest speeds at large queue depths.
  • \n
\n

These are not surprising and are consistent with the test results on macOS laptops with direct external SSDs and HDDs, only with the lower numbers. With this NAS system, both the SSD and HDD are connected via the USB 3.0 interface. USB 3.0 supports transfer speeds of up to 5Gbit/s, so the performance bottleneck of the system is the network interface bandwidth and processor power.

\n

That being said, for both SSDs and HDDs, the transfer speeds have been more than 900Mbit/s at 1MB sequential read and queue depth 8, close to the upper bandwidth limit of the Gigabit Ethernet interface. This read speed can support a single 1080p60 video stream at a frame rate of 60fps or 2 parallel 1080i50 video streams at a frame rate of 25fps, which is sufficient for home streaming services. In another media service test, the NAS system performs satisfactorily with three computers playing HD video on demand and one phone playing MP3 music without any lag.

\n

Project Summary

\n

This completes our Raspberry Pi home NAS project. Now we can move our NAS to a more permanent location to provide network file and streaming services for the whole family.

\n

\n

Economically, our home NAS has the cost summarized in the table below (excluding SSD/HDD)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
DevicesFunctionsCost($)
Raspberry Pi 4B 2/4/8GB RAMPrimary hardware system45/55/75
Samsung 32GB EVO+ Class-10 Micro SDHCOS storage10
Geekworm NASPi Raspberry Pi 4B NAS Storage KitCase, extending board and PWM fan60
Geekworm 20W 5V 4A USB-C Power AdaptorPower supply15
TP-Link TL-SG105 5-Port Gigabit Ethernet SwitchDesktop switch15
\n

Even with the choice of 8GB RAM Raspberry Pi 4B, the whole cost is only $175, a little more than half of the price of the low-end brand NAS sold in the market. Unless there are a lot of client devices that need streaming services, the memory consumption is usually under 2GB, so the 2GB Raspberry Pi 4B should be able to work in most home scenarios. That cuts the cost down to $145, less than half the MSRP.

\n

On the other hand, this DIY project was a very good exercise of hands-on practice, helping us gain valuable intuitive experience in building network connections, configuring system hardware and software, and tuning and testing application layer services. To sum up, the home NAS system built with Raspberry Pi 4B and OMV, combined with a Plex media server, provides a cost-effective solution for file backup and streaming media services in the home network.

\n

Appendix: List of related devices and Amazon links

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n

CanaKit Raspberry Pi 4B 8GB RAM + 128GB MicroSD Extrem Kit https://amzn.to/3DUeDfm
\nSamsung 32GB EVO+ Class 10 Micro SDHC with Adapter https://amzn.to/3FLkTb7
\nGeekworm NASPi 2.5\" SATA HDD/SSD Raspberry Pi 4B NAS Storage Kit https://amzn.to/3m5djAi
\nGeekworm Raspberry Pi 4 20W 5V 4A USB-C Power Adaptor https://amzn.to/3m1EXOf
\nTP-Link TL-SG105 5-Port Gigabit Ethernet Switch https://amzn.to/3pRkBsi
\nSamsung 870 EVO 500GB 2.5\" SATA III Internal SSD https://amzn.to/3DPKnCl
\nSeagate Portable 2TB USB 3.0 External HDD https://amzn.to/3EYegl4
\nSynology 2-Bay 2GB NAS DiskStation DS220+ https://amzn.to/3Jp5qjd
\nSynology 5-Bay 8GB NAS DiskStation DS1520+ https://amzn.to/3qniQDm

\n
\n","categories":["DIY Projects"],"tags":["Raspberry Pi","NAS"]},{"title":"RSA: Attack and Defense (II)","url":"/en/2023/11/17/RSA-attack-defense-2/","content":"

This article first supplements two specific integer factorization methods - Fermat's factorization method and Pollard's rho algorithm, explaining the essence of their algorithms and applicable scenarios, and provides a Python reference implementation. Next, it analyzes in detail a classic low private exponent attack - Wiener's attack, elaborating on the mathematical basis, the attack principle, and the attack procedure, with a complete Python program. The article also cites the latest research paper proposing a new upper bound for the private exponent when Wiener's attack is successful and verifies the correctness of this limit with a test case.

\n

The enemy knows the system being used.
Claude Shannon (American mathematician, electrical engineer, computer scientist, and cryptographer known as the \"father of information theory\".)

\n
\n

Previous article: RSA: Attack and Defense (I)

\n

Integer Factorization (Supplementary)

\n

Even if the RSA modulus \\(N\\) is a very big number (with sufficient bits), problems can still arise if the gap between the prime factors \\(p\\) and \\(q\\) is too small or too large. In such cases, there are specific factorization algorithms that can effectively retrieve p and q from the public modulus N.

\n

Fermat's Factorization Method

\n

When the prime factors \\(p\\) and \\(q\\) are very close, Fermat's factorization method can factorize the modulus N in a very short time. Fermat's factorization method is named after the French mathematician Pierre de Fermat. Its base point is that every odd integer can be represented as the difference between two squares, i.e. \\[N=a^2-b^2\\] Applying algebraic factorization on the right side yields \\((a+b)(a-b)\\). If neither factor is one, it is a nontrivial factor of \\(N\\). For the RSA modulus \\(N\\), assuming \\(p>q\\), correspondingly \\(p=a+b\\) and \\(q=a-b\\). In turn, it can be deduced that \\[N=\\left({\\frac {p+q}{2}}\\right)^{2}-\\left({\\frac {p-q}{2}}\\right)^{2}\\] The idea of Fermat's factorization method is to start from \\(\\lceil{\\sqrt N}\\rceil\\) and try successive values of a, then verify if \\(a^{2}-N=b^{2}\\). If it is true, the two nontrivial factors \\(p\\) and \\(q\\) are found. The number of steps required by this method is approximately \\[{\\frac{p+q}{2}}-{\\sqrt N}=\\frac{({\\sqrt p}-{\\sqrt q})^{2}}{2}=\\frac{({\\sqrt N}-q)^{2}}{2q}\\] In general, Fermat's factorization method is not much better than trial division. In the worst case, it may be slower. However, when the difference between \\(p\\) and \\(q\\) is not large, and \\(q\\) is very close to \\(\\sqrt N\\), the number of steps becomes very small. In the extreme case, if the difference between \\(q\\) and \\(\\sqrt N\\) is less than \\({\\left(4N\\right)}^{\\frac 1 4}\\), this method only takes one step to finish.

\n

Below is a Python implementation of Fermat's factorization method, and an example of applying it to factorize the RSA modulus N:

\n
import gmpy2
import time

def FermatFactor(n):
assert n % 2 != 0

a = gmpy2.isqrt(n) + 1
b2 = gmpy2.square(a) - n

while not gmpy2.is_square(b2):
a += 1
b2 = gmpy2.square(a) - n

b = gmpy2.isqrt(b2)
return a + b, a - b

p = 7422236843002619998657542152935407597465626963556444983366482781089760760914403641211700959458736191688739694068306773186013683526913015038631710959988771
q = 7422236843002619998657542152935407597465626963556444983366482781089760759017266051147512413638949173306397011800331344424158682304439958652982994939276427
N = p * q
print("N =", N)

start = time.process_time()
(p1, q1) = FermatFactor(N)
end = time.process_time()
print(f'Elapsed time {end - start:.3f}s.')

assert(p == p1)
assert(q == q1)
\n

The FermatFactor() function defined at the beginning of the program implements the Fermat factorization method. It calls three library functions of gmpy2: isqrt() to find the square root of an integer, square() to execute the squaring operation, and is_square() to verify if the input is a square number. Two large prime numbers \\(p\\) and \\(q\\) of 154 decimal digits each are defined later, and multiplying them gives \\(N\\). Then \\(N\\) is fed into the FermatFactor() function and the program starts timing. When the function returns, it prints the elapsed time and confirms the factorization.

\n
N = 55089599753625499150129246679078411260946554356961748980861372828434789664694269460953507615455541204658984798121874916511031276020889949113155608279765385693784204971246654484161179832345357692487854383961212865469152326807704510472371156179457167612793412416133943976901478047318514990960333355366785001217
Elapsed time 27.830s.
\n

As can be seen, in less than half a minute, this large number of 308 decimal digits (about 1024 bits) was successfully factorized! Going back and examining \\(p\\) and \\(q\\), one can see that the first 71 digits of these two large prime numbers of 154 decimal digits are exactly the same. This is exactly the scenario in which the Fermat factorization method exerts its power. If you simply modify the FermatFactor() function to save the starting \\(a\\) value and compare it to the value at the end of the loop, you get a loop count of 60613989. With such a small number value, it's no wonder that the factorization is done so quickly.

\n

Therefore, the choice of the large prime numbers \\(p\\) and \\(q\\) must not only be random but also be far enough apart. After obtaining two large prime numbers, the difference between them shall be checked. If it is too small, regeneration is required to prevent attackers from using Fermat's factorization method to crack it.

\n

Pollard's Rho Algorithm

\n

On the opposite end, if the gap between the large prime factors \\(p\\) and \\(q\\) is too large, they may be cracked by Pollard's rho algorithm. This algorithm was invented by British mathematician John Pollard1 in 1975. It requires only a small amount of storage space, and its expected running time is proportional to the square root of the smallest prime factor of the composite number being factorized.

\n

The core idea of Pollard's rho algorithm is to use the collision pattern of traversal sequences to search for factors, and its stochastic and recursive nature allows it to factorize integers efficiently in relatively low complexity. First, for \\(N=pq\\), assume that \\(p\\) is the smaller nontrivial factor. The algorithm defines a polynomial modulo \\(N\\) \\[f(x)=(x^{2}+c){\\pmod N}\\] A pseudorandom sequence can be generated by making recursive calls with this polynomial, and the sequence generation formula is \\(x_{n+1}=f(x_n)\\). For example, given an initial value of \\(x_0=2\\) and a constant \\(c=1\\), it follows that \\[\\begin{align}\nx_1&=f(2)=5\\\\\nx_2&=f(x_1)=f(f(2))=26\\\\\nx_3&=f(x_2)=f(f(f(2)))=677\\\\\n\\end{align}\\] For two numbers \\(x_i\\) and \\(x_j\\) in the generated sequence, \\(|x_i-x_j|\\) must be a multiple of \\(p\\) if \\(x_i\\neq x_j\\) and \\(x_i\\equiv x_j{\\pmod p}\\). In this case, calculating \\(\\gcd(|x_i-x_j|,N)\\) results in \\(p\\). Based on the Birthday Paradox, in the worst case, it is expected that after generating about \\(\\sqrt p\\) numbers, there will be two numbers that are the same under the modulus \\(p\\), thus successfully factorizing \\(N\\). However, the time complexity of performing pairwise comparisons is still unsatisfactory. In addition, storing so many numbers is also troublesome when N is large.

\n

How to solve these problems? This is where the ingenuity of Pollard's rho algorithm lies. Pollard found that the sequence generated by this pseudorandom number generator has two properties:

\n
    \n
  1. Since each number depends only on the value that precedes it, and the numbers generated under the modular operation are finite, sooner or later it will enter a cycle. As shown below, the resulting sequence will eventually form a directed graph similar in shape to the Greek letter \\(\\rho\\), from which the algorithm takes its name. \"Cycle
  2. \n
  3. When \\(|x_i-x_j| \\equiv 0 \\pmod p\\), there must be \\[|f(x_i)-f(x_j)|=|{x_i}^2-{x_j}^2|=|x_i+x_j|\\cdot|x_i-x_j|\\equiv 0 \\pmod p\\] This shows that if two numbers in the sequence satisfy a certain condition under modulus operation, all equally spaced pairs of numbers satisfy the same condition.
  4. \n
\n

Insightful of these two properties, Pollard utilizes Floyd's cycle-finding algorithm (also known as the tortoise and hare algorithm) to set up the fast and slow nodes \\(x_h\\) and \\(x_t\\). Starting from the same initial value \\(x_0\\), the slow node \\(x_t\\) moves to the next node in the sequence every step, while the fast node \\(x_h\\) moves forward by two nodes at a time, i.e. \\[\\begin{align}\nx_t&=f(x_t)\\\\\nx_h&=f(f(x_h))\\\\\n\\end{align}\\] After that, calculate \\(\\gcd(|x_h-x_t|,N)\\), and the result that is greater than 1 and less than \\(N\\) is \\(p\\), otherwise continue with the same steps. With this design, since each move is equivalent to checking a new node spacing, pairwise comparisons are unnecessary. If not found, eventually the fast and slow nodes will meet on the cycle, at which time the result of finding the greatest common divisor is \\(N\\). The algorithm's recommendation at this point is to exit and regenerate the pseudorandom number sequence with a different initial value or constant \\(c\\) and try again.

\n

This is the classic Pollard's rho algorithm. Its time complexity is \\(𝑂(\\sqrt p\\log N)\\) (\\(\\log\\) comes from the required \\(\\gcd\\) operations). For RSA modulus \\(N\\), obviously \\(p\\leq \\sqrt N\\), so the upper bound on the time complexity can be written as \\(𝑂(N^{\\frac 1 4}\\log N)\\). The time complexity expression for Pollard's rho algorithm indicates that the smaller the minimum prime factor of the composite number being factorized, the faster the factorization is expected to be. An excessively small \\(p\\) is extremely unsafe.

\n

Programming Pollard's rho algorithm is not difficult. The following Python code shows a function implementation of the algorithm, PollardRhoFactor(), and some test cases

\n
import gmpy2
import time

def PollardRhoFactor(n, seed, c):

if n % 2 == 0: return 2
if gmpy2.is_prime(n): return n

while True:
f = lambda x: (x**2 + c) % n
t = h = seed
d = 1

while d == 1:
t = f(t) # Tortoise
h = f(f(h)) # Hare
d = gmpy2.gcd(h - t, n)

if d != n:
return d # find a non-trivial factor

# start a new round with updated seed and c
seed = h
c += 1

N = [10967535067, 18446744073709551617, 97546105601219326301,
780002082420246798979794021150335143]

print(f"{'N':<37}{'P':<16}{'Elapsed Time (s)':}")
for i in range(0, len(N)):
start = time.process_time()
p = PollardRhoFactor(N[i], 2, 1)
end = time.process_time()
print(f'{N[i]:<37}{p:<16}{end - start:16.3f}')

F8 = 2**(2**8) + 1 # A 78-digit Fermat number
start = time.process_time()
p = PollardRhoFactor(F8, 2, 1)
end = time.process_time()
print(f'\\nF8 = {F8}\\np = {p}\\nElapsed time {end - start:.3f}s')
\n

The function PollardRhoFactor() accepts three arguments: n is the composite number to be factorized, seed is the initial value of the pseudorandom sequence, and c is the constant value in the generating polynomial. The function internally uses two while to form a double loop: inside the outer loop defines the generating polynomial f and the fast and slow nodes h and t, while the node moving steps and the greatest common divisor operation are implemented in the inner loop. The inner loop ends only if the greatest common divisor d is not 1. At this point, if d is not equal to n, the function returns the non-trivial factor d. Otherwise, d equals n, meaning the fast and slow nodes have met on the cycle. In this situation, the code in the outer loop resets seed to the value of the fast node and increments c, thus restarting a new round of search.

\n

Running the above code on a MacBook Pro (2019), the output is as follows

\n
N                                    P               Elapsed Time (s)
10967535067 104729 0.001
18446744073709551617 274177 0.002
97546105601219326301 9876543191 0.132
780002082420246798979794021150335143 244300526707007 6.124

F8 = 115792089237316195423570985008687907853269984665640564039457584007913129639937
p = 1238926361552897
Elapsed time 64.411s
\n

This result proves the effectiveness of Pollard's rho algorithm. In particular, for the last test, the input to the function was the Fermat number \\(F_8\\) (defined as \\(F_{n}=2^{2^{n}}+1\\), where \\(n\\) is a non-negative integer). In 1980, Pollard and Australian mathematician Richard Brent 2 working together applied this algorithm to factorize \\(F_8\\) for the first time. The factorization took 2 hours on a UNIVAC 1100/42 computer. And now, on a commercial off-the-shelf laptop computer, Pollard's rho algorithm revealed the smaller prime factor 1238926361552897 of \\(F_8\\) in 64.4 seconds.

\n

Subsequently, Pollard and Brent made further improvements to the algorithm. They observed that if \\(\\gcd(d, N)>1\\), for any positive integer \\(k\\), there is also \\(\\gcd(kd, N)>1\\). So multiplying \\(k\\) consecutive \\((|x_h-x_t| \\pmod N)\\) and taking the modulo \\(N\\) with the product, and then solving for the greatest common divisor with \\(N\\) should obtain the same result. This method replaces \\(k\\) times \\(\\gcd\\) with \\((k-1)\\) times multiplications modulo \\(N\\) and a single \\(\\gcd\\), thus achieving acceleration. The downside is that occasionally it may cause the algorithm to fail by introducing a repeated factor. When this happens, it then suffices to reset \\(k\\) to 1 and fall back to the regular Pollard's rho algorithm.

\n

The following Python function implements the improved Pollard's rho algorithm. It adds an extra for loop to implement the multiplication of \\(k\\) consecutive differences modulo \\(N\\), with the resulting product stored in the variable mult. mult is fed to the greatest common divisor function with \\(N\\), and the result is assigned to d for further check. If this fails, \\(k\\) is set to 1 in the outer loop.

\n
def PollardRhoFactor2(n, seed, c, k):

if n % 2 == 0: return 2
if gmpy2.is_prime(n): return n

while True:
f = lambda x: (x**2 + c) % n
t = h = seed
d = 1

while d == 1:
mult = 1
for _ in range(k):
t = f(t) # Tortoise
h = f(f(h)) # Hare
mult = (mult * abs(h - t)) % n

d = gmpy2.gcd(mult, n)

if d != n:
return d # find a non-trivial factor

# start a new round with updated seed and c
seed = h
c += 1
k = 1 # fall back to regular rho algorithm

print(f"{'N':<37}{'P':<16}{'Elapsed Time (s)':}")
for i in range(0, len(N)):
start = time.process_time()
p = PollardRhoFactor2(N[i], 2, 1, 100)
end = time.process_time()
print(f'{N[i]:<37}{p:<16}{end - start:16.3f}')

F8 = 2**(2**8) + 1 # A 78-digit Fermat number
start = time.process_time()
p = PollardRhoFactor2(F8, 2, 1, 100)
end = time.process_time()
print(f'\\nF8 = {F8}\\np = {p}\\nElapsed time {end - start:.3f}s')
\n

Using the same test case, called with \\(k\\) set to 100, the program runs as follows

\n
N                                    P               Elapsed Time (s)
10967535067 104729 0.001
18446744073709551617 274177 0.002
97546105601219326301 9876543191 0.128
780002082420246798979794021150335143 244300526707007 5.854

F8 = 115792089237316195423570985008687907853269984665640564039457584007913129639937
p = 1238926361552897
Elapsed time 46.601s
\n

It can be seen that for relatively small composite \\(N\\), the improvement is not significant. As \\(N\\) becomes larger, the speedup is noticeable. For the 78-bit decimal Fermat number \\(F_8\\), the improved Pollard's rho algorithm takes only 46.6 seconds, which is a speedup of more than 27% over the regular algorithm. The improved Pollard \\(\\rho\\) algorithm indeed brings significant speedup.

\n

To summarize the above analysis, implementation, and testing of Pollard's rho algorithm, it is necessary to set a numerical lower bound for the generated prime numbers \\(p\\) and \\(q\\) to be used by RSA. If either of them is too small, it must be regenerated or it may be cracked by an attacker applying Pollard's rho algorithm.

\n

Low Private Exponent Attack

\n

For some particular application scenarios (e.g., smart cards and IoT), limited by the computational capability and low-power requirements of the device, a smaller value of private exponent \\(d\\) is favored for fast decryption or digital signing. However, a very low private exponent is very dangerous, and there are some clever attacks that can totally breach such an RSA cryptosystem.

\n

Wiener's Attack

\n

In 1990, Canadian cryptographer Michael J. Wiener conceived an attack scheme3 based on continued fraction approximation that can effectively recover the private exponent \\(d\\) from the RSA public key \\((N, e)\\) under certain conditions. Before explaining how this attack works, it is important to briefly introduce the concept and key properties of continued fraction.

\n

Continued Fraction

\n

The continuous fraction itself is just a mathematical expression, but it introduces a new perspective on the study of real numbers. The following is a typical continued fraction \\[x = a_0 + \\cfrac{1}{a_1 + \\cfrac{1}{a_2 + \\cfrac{1}{\\ddots\\,}}}\\] where \\(a_{0}\\) is an integer and all other \\(a_{i}(i=1,\\ldots ,n)\\) are positive integers. One can abbreviate the continued fraction as \\(x=[a_0;a_1,a_2,\\ldots,a_n]\\). Continued fractions have the following properties:

\n
    \n
  1. Every rational number can be expressed as a finite continued fraction, i.e., a finite number of \\(a_{i}\\). Every rational number has an essentially unique simple continued fraction representation with infinite terms. Here are two examples: \\[\\begin{align}\n\\frac {68} {75}​&=0+\\cfrac {1} {1+\\cfrac {1} {\\small 9+\\cfrac {1} {\\scriptsize 1+\\cfrac {1} {2+\\cfrac {1} {2}}}}}=[0;1,9,1,2,2]\\\\\nπ&=[3;7,15,1,292,1,1,1,2,…]\n\\end{align}\\]

  2. \n
  3. To calculate the continued fraction representation of a positive rational number \\(f\\), first subtract the integer part of \\(f\\), then find the reciprocal of the difference and repeat till the difference is zero. Let \\(a_i\\) be the integer quotient, \\(r_i\\) be the difference of the \\(i\\)th step, and \\(n\\) be the number of steps, then \\[\\begin{align}\na_0 &= \\lfloor f \\rfloor, &r_0 &= f - a_0\\\\\na_i&={\\large\\lfloor} \\frac 1 {r_{i-1}} {\\large\\rfloor}, &r_i &=\\frac 1 {r_{i-1}} - a_i \\quad (i = 1, 2, ..., n)\\\\\n\\end{align}\\] The corresponding Python function implementing the continued fraction expansion of rationals is as follows

    \n

    def cf_expansion(nm: int, dn:int) -> list:
    """ Continued Fraction Expansion of Rationals
    Parameters:
    nm - nominator
    dn - denomainator
    Return:
    List for the abbreviated notation of the continued fraction
    """
    cf = []
    a, r = nm // dn, nm % dn
    cf.append(a)

    while r != 0:
    nm, dn = dn, r
    a = nm // dn
    r = nm % dn
    cf.append(a)

    return cf

  4. \n
  5. For both rational and irrational numbers, the initial segments of their continued fraction representations produce increasingly accurate rational approximations. These rational numbers are called the convergents of the continued fraction. The even convergents continually increase, but are always less than the original number; while the odd ones continually decrease, but are always greater than the original number. Denote the numerator and denominator of the \\(i\\)-th convergent as \\(h_i\\) and \\(k_i\\) respectively, and define \\(h_{-1}=1,h_{-2}=0\\) and \\(k_{-1}=0,k_{-2}=1\\), then the recursive formula for calculating the convergents is \\[\\begin{align}\n\\frac {h_0} {k_0} &= [0] = \\frac 0 1 = 0<\\frac {68} {75}\\\\\n\\frac {h_1} {k_1} &= [0;1] = \\frac 1 1 = 1>\\frac {68} {75}\\\\\n\\frac {h_2} {k_2} &= [0;1,9] = \\frac 9 {10}<\\frac {68} {75}\\\\\n\\frac {h_3} {k_3} &= [0;1,9,1] = \\frac {10} {11}>\\frac {68} {75}\\\\\n\\frac {h_4} {k_4} &= [0;1,9,1,2] = \\frac {29} {32}<\\frac {68} {75}\\\\\n\\end{align}\\] It can be verified that these convergents satisfy the aforementioned property and are getting closer to the true value. The following Python function implements a convergent generator for a given concatenated fraction expansion, and it returns a tuple of objects consisting of the convergent's numerator and denominator.

    \n

    def cf_convergent(cf: list) -> (int, int):
    """ Calculates the convergents of a continued fraction
    Parameters:
    cf - list for the continued fraction expansion
    Return:
    A generator object of the convergent tuple
    (numerator, denominator)
    """
    nm = [] # Numerator
    dn = [] # Denominators

    for i in range(len(cf)):
    if i == 0:
    ni, di = cf[i], 1
    elif i == 1:
    ni, di = cf[i]*cf[i-1] + 1, cf[i]
    else: # i > 1
    ni = cf[i]*nm[i-1] + nm[i-2]
    di = cf[i]*dn[i-1] + dn[i-2]

    nm.append(ni)
    dn.append(di)
    yield ni, di

  6. \n
  7. Regarding the convergents of continued fractions, there is also an important Legendre4 theorem: Let \\(a∈ \\mathbb Z, b ∈ \\mathbb Z^+\\) such that \\[\\left\\lvert\\,f - \\frac a b\\right\\rvert< \\frac 1 {2b^2}\\] then \\(\\frac a b\\) is a convergent of the continued fraction of \\(f\\).

  8. \n
\n

Attack Mechanism

\n

Now analyze how Wiener's attack works. From the relationship between RSA public and private exponent \\(ed\\equiv 1 {\\pmod {\\varphi(N)}}\\), it can be deduced that there exists an integer \\(k\\) such that \\[ed - k\\varphi(N) = 1\\] Dividing both sides by \\(d\\varphi(N)\\) gives \\[\\left\\lvert\\frac e {\\varphi(N)} - \\frac k d\\right\\rvert = \\frac 1 {d{\\varphi(N)}}\\] Careful observation of this formula reveals that because \\(\\varphi(N)\\) itself is very large, and \\(\\gcd(k,d)=1\\), \\(\\frac k d\\) is very close to \\(\\frac e {\\varphi(N)}\\). In addition, \\[\\varphi(N)=(p-1)(q-1)=N-(p+q)+1\\] Its difference from \\(N\\) is also relatively small. Therefore, \\(\\frac k d\\) and \\(\\frac e N\\) also do not differ by much. Since RSA's \\((N,e)\\) are public, Wiener boldly conceived - if \\(\\pmb{\\frac e N}\\) is expanded into a continued fraction, it is possible that \\(\\pmb{\\frac k d}\\) is one of its convergents!

\n

So how to verify if a certain convergent is indeed \\(\\frac k d\\)? With \\(k\\) and \\(d\\), \\(\\varphi (N)\\) can be calculated, thereby obtaining \\((p+q)\\). Since both \\((p+q)\\) and \\(pq\\) are known, constructing a simple quadratic equation5 can solve for \\(p\\) and \\(q\\). If their product equals \\(N\\), then \\(k\\) and \\(d\\) are correct and the attack succeeds.

\n

What are the conditions for Wiener's attack to work? Referring to Legendre's theorem mentioned above, it can be deduced that if \\[\\left\\lvert\\frac e N - \\frac k d\\right\\rvert < \\frac 1 {2{d^2}}\\] then \\(\\frac k d\\) must be a convergent of \\(\\frac e N\\). This formula can also be used to derive an upper bound of the private exponent d for a feasible attack. Wiener's original paper states the upper bound as \\(N^{\\frac 1 4}\\), but without detailed analysis. In 1999, American cryptographer Dan Boneh6 provided the first rigorous proof of the upper bound, showing that under the constraints \\(q<p<2q\\) and \\(e<\\varphi(N)\\), Wiener's attack applies for \\(d<\\frac 1 3 N^{\\frac 1 4}\\). In a new paper published in 2019, several researchers at the University of Wollongong in Australia further expanded the upper bound under the same constraints to \\[d\\leq \\frac 1 {\\sqrt[4]{18}} N^\\frac 1 4=\\frac 1 {2.06...}N^\\frac 1 4\\]

\n

Note that for simplicity, the above analysis of Wiener's attack mechanism is based on the Euler phi function \\(\\varphi (N)\\). In reality, RSA key pairs are often generated using the Carmichael function \\(\\lambda(N)\\). The relationship between the two is: \\[\\varphi (N)=\\lambda(n)\\cdot\\gcd(p-1,q-1)\\] It can be proven that starting from \\(ed≡1{\\pmod{\\lambda(N)}}\\), the same conclusions can be reached. Interested readers may refer to Wiener's original paper for details.

\n

Attack Workflow

\n

With an understanding of the mechanism of Wiener's attack, the attack workflow can be summarized as follows:

\n
    \n
  1. Expand \\(\\frac e N\\) into a continued fraction
  2. \n
  3. Generate the sequence of successive convergents of this continued fraction.
  4. \n
  5. Iteratively check each convergent's numerator \\(k\\) and denominator \\(d\\):\n
      \n
    • If \\(k\\) is zero, or \\(d\\) is even, or \\(ed\\not\\equiv 1 \\pmod k\\), skip this convergent.
    • \n
    • Calculate \\(\\varphi (N) = \\frac {ed-1} k\\), and solve for the integer roots p and q of the quadratic equation \\(x^2−(N−φ(N)+1)x+N\\).
    • \n
    • Verify if \\(N = p \\cdot q\\), if true, the attack succeeds and return \\((p, q, d)\\); otherwise continue.
    • \n
    • If all convergents are checked and no match, Wiener's attack fails.
    • \n
  6. \n
\n

The complete Python implementation is as follows:

\n
import gmpy2
import random

def solve_rsa_primes(s: int, m: int) -> tuple:
""" Solve RSA prime numbers (p, q) from the quadratic equation
p^2 - s * p + m = 0 with the formula p = s/2 +/- sqrt((s/2)^2 - m)
Parameters:
s - sum of primes (p + q)
m - product of primes (p * q)
Return: (p, q)
"""
half_s = s >> 1
tmp = gmpy2.isqrt(half_s ** 2 - m)
return int(half_s + tmp), int(half_s - tmp)

def wiener_attack(n: int, e: int) -> (int, int, int):
""" Wiener's Attack on RSA public key cryptosystem
Parameters:
N - RSA modulus N = p*q
e - RSA public exponent
Return:
A tuple of (p, q, d)
p, q - the two prime factors of RSA modulus N
d - RSA private exponent
"""
cfe = cf_expansion(e, n) # Convert e/n into a continued fraction
cvg = cf_convergent(cfe) # Get all of its convergents

for k, d in cvg:
# Check if k and d meet the requirements
if k == 0 or d % 2 == 0 or (e * d) % k != 1:
continue

# assume ed ≡ 1 (mod ϕ(n))
phi = (e * d - 1) // k
p, q = solve_rsa_primes(n - phi + 1, n)
if n == p * q:
return p, q, d

return None

def uint_to_bytes(x: int) -> bytes:
""" This works only for unsigned (non-negative) integers.
It does not work for 0."""
if x == 0:
return bytes(1)
return x.to_bytes((x.bit_length() + 7) // 8, 'big')

N = int(
'6727075990400738687345725133831068548505159909089226'\\
'9093081511054056173840933739311418333016536024767844'\\
'14065504536979164089581789354173719785815972324079')

e = int(
'4805054278857670490961232238450763248932257077920876'\\
'3637915365038611552743522891345050097418639182479215'\\
'15546177391127175463544741368225721957798416107743')

c = int(
'5928120944877154092488159606792758283490469364444892'\\
'1679423458017133739626176287570534122326362199676752'\\
'56510422984948872954949616521392542703915478027634')

p, q, d = wiener_attack(N, e)
assert not d is None, "Wiener's Attack failed!"
print("p =", p)
print("q =", q)
print("d =", d)
print(uint_to_bytes(pow(c, d, N)))

N = int(
'22836858353287668091920368816286415778103964252589'\\
'28295130420474999022996621982166664596581454018899'\\
'48429922376560732622754871538043874356270300826321'\\
'16650572564937978011181394388679265524940467869924'\\
'85473650038355720409426235584833584188449224331698'\\
'63569900296911605460645581176522325967221393273906'\\
'69673188457131381644120787783215342848744792830245'\\
'01805598140668893320307200136190794138325132168722'\\
'14217943474001731747822701596634040292342194986951'\\
'94551646668806852454006312372413658692027515557841'\\
'41440661232146905186431357112566536770669381756925'\\
'38179415478954522854711968599279014482060579354284'\\
'55238863726089083')

e = int(
'17160819308904585327789016134897914235762203050367'\\
'34632679585567058963995675965428034906637374660531'\\
'64750599687461192166424505919293706011293378320096'\\
'43372382766547546926535697752805239918767190684796'\\
'26509298669049485976118315666126871681847641670872'\\
'58895073919139366379901867664076540531765577090231'\\
'67209821832859747419658344363466584895316847817524'\\
'24703257392651850823517297420382138943770358904660'\\
'59442300191228592937251734592732623207324742303631'\\
'32436274414264865868028527840102483762414082363751'\\
'87208612632105886502393648156776330236987329249988'\\
'11429508256124902530957499338336903951924035916501'\\
'53661610070010419')

d = wiener_attack(N, e)
assert not d is None, "Wiener's attack failed!"
print("d =", d)

old_b = int(gmpy2.root(N, 4)/3)
new_b = int(gmpy2.root(N, 4)/gmpy2.root(18, 4))
print("old_b =", old_b)
print("new_b =", new_b)
assert d > old_b and d <= new_b
\n

The code above ends with two test cases. Referring to the program output below, the first test case gives a small RSA modulus \\(N\\) and a relatively large \\(e\\), which is precisely the scenario where Wiener's attack comes into play. The program calls the attack function wiener_attack() that quickly returns \\(d\\) as 7, then decrypts a ciphertext and recovers the original plaintext \"Wiener's attack success!\".

\n

The second test case sets a 2048-bit \\(N\\) and \\(e\\), and Wiener's attack also succeeds swiftly. The program also verifies that the cracked \\(d\\) (511 bits) is greater than the old bound old_b (\\(N^{\\frac 1 4}\\)), but slightly less than the new bound new_b (\\(\\frac 1 {\\sqrt[4]{18}} N^\\frac 1 4\\)). This confirms the conclusion of the University of Wollongong researchers.

\n
p = 105192975360365123391387526351896101933106732127903638948310435293844052701259
q = 63949859459297920725542167940404754256294386312715512490347273751054137071981
d = 7
b"Wiener's attack success!"
d = 5968166949079360555220268992852191823920023811474288738674370592596189517443887780023653031793516493806462114248181371416016184480421640973439863346079123
old_b = 4097678063688683751669784036917434915284399064709500941393388469932708726583832656910141469383433913840738001283204519671690533047637554279688711463501824
new_b = 5968166949079360962136673400587903792234115710617172051628964885379180548131448950677569697264501402772121272285767654845001503996650347315559383468867584
\n

These two test cases prove the effectiveness and prerequisites of Wiener's attack. To prevent Wiener's attack, the RSA private exponent \\(d\\) must be greater than the upper bound. Choosing \\(d\\) no less than \\(N^{\\frac 1 2}\\) is a more prudent scheme. In practice, the optimized decryption using Fermat's theorem and Chinese remainder theorem is often used, so that even larger \\(d\\) can achieve fast decryption and digital signing.

\n
\n

To be continued, stay tuned for the next article: RSA: Attack and Defense (III)

\n
\n
\n
\n
    \n
  1. John Pollard, a British mathematician, the recipient of 1999 RSA Award for Excellence in Mathematics for major contributions to algebraic cryptanalysis of integer factorization and discrete logarithm.↩︎

  2. \n
  3. Richard Peirce Brent, an Australian mathematician and computer scientist, an emeritus professor at the Australian National University.↩︎

  4. \n
  5. M. Wiener, “Cryptanalysis of short RSA secret exponents,” IEEE Trans. Inform. Theory, vol. 36, pp. 553–558, May 1990↩︎

  6. \n
  7. Adrien-Marie Legendre (1752-1833), a French mathematician who made numerous contributions to mathematics.↩︎

  8. \n
  9. Refer to Solve picoCTF's RSA Challenge Sum-O-Primes↩︎

  10. \n
  11. Dan Boneh, an Israeli–American professor in applied cryptography and computer security at Stanford University, a member of the National Academy of Engineering.↩︎

  12. \n
\n
\n","categories":["Technical Know-how"],"tags":["Cryptography","Network Security","Python Programming"]},{"title":"RSA: Attack and Defense (I)","url":"/en/2023/03/16/RSA-attack-defense/","content":"

RSA is a public-key cryptosystem built on top of an asymmetric encryption algorithm, which was jointly invented by three cryptographers and computer scientists at the Massachusetts Institute of Technology in 1977. The RSA public-key encryption algorithm and cryptosystem provide data confidentiality and signature verification functions widely used on the Internet. Since its birth, RSA has become a major research object of modern cryptography. Many cryptanalysts and information security experts have been studying its possible theoretical flaws and technical loopholes to ensure security and reliability in practical applications.

\n

There are certain things whose number is unknown. If we count them by threes, we have two left over; by fives, we have three left over; and by sevens, two are left over. How many things are there?
Sunzi Suanjing, Volume 2.26

\n
\n

Fortunately, after more than 40 years of extensive research and practical application tests, although many sophisticated attack methods have been discovered, RSA is generally safe. These attack methods all take advantage of the improper use of RSA or the vulnerability of software and hardware implementations, and cannot shake the security foundation of its encryption algorithm. On the other hand, the research on these attack methods shows that implementing a safe and robust RSA application is not a simple task. A consensus in cryptography and network security hardware and software engineering practice is: never roll your own cryptography!1 The appropriate solution is to use an existing, well-tested, and reliably maintained library or API to implement the RSA algorithm and protocol application.

\n

Here is a brief survey of the common means of attacking RSA, the mathematical mechanism on which the attack is based, and the corresponding protective measures. Referring to the previous article, let’s start by reviewing the working mechanism and process of RSA:

\n
    \n
  1. Choose two large prime numbers \\(p\\) and \\(q\\), compute \\(N=pq\\)
  2. \n
  3. Compute \\(\\lambda(N)\\), where \\(\\lambda\\) is Carmichael's totient function\n
      \n
    • When both \\(p\\) and \\(q\\) are prime, \\(\\lambda(pq)=\\operatorname {lcm}(p − 1, q − 1)\\)
    • \n
    • \\(\\operatorname{lcm}\\) is a function to find the least common multiple, which can be calculated by the Euclidean algorithm
    • \n
  4. \n
  5. Choose a number \\(e\\) that is less than \\(\\lambda(N)\\) and also coprime with it, then calculate the modular multiplicative inverse of \\(e\\) modulo \\(\\lambda(N)\\). That is \\(d\\equiv e^{-1}\\pmod {\\lambda(N)}\\)\n
      \n
    • Per the definition of modular multiplicative inverse, find \\(d\\) such that \\((d⋅e)\\bmod\\lambda(N)=1\\)
    • \n
    • A modular multiplicative inverse can be found by using the extended Euclidean algorithm
    • \n
  6. \n
  7. \\(\\pmb{(N,e)}\\) is the public key\\(\\pmb{(N,d)}\\) is the private key\n
      \n
    • The public key can be known by everyone, but the private key must be kept secret
    • \n
    • The records of \\(p,q,\\lambda(N)\\) can all be discarded
    • \n
  8. \n
  9. The sender first converts the message into a positive integer less than \\(N\\) according to the agreed encoding format, then uses the receiver's public key to compute the ciphertext with the formula \\(\\pmb{c\\equiv m^e\\pmod N}\\)
  10. \n
  11. After receiving the ciphertext, the receiver uses its private key to compute the plaintext \\(m\\) with the formula \\(\\pmb{m\\equiv c^d\\pmod N}\\), then decodes it into the original message
  12. \n
  13. A message encrypted with the private key can also be decrypted by the public key, i.e. if \\(\\pmb{s\\equiv m^d\\pmod N}\\), \\(\\pmb{m\\equiv s^e\\pmod N}\\). This is the supported digital signature feature
  14. \n
\n

Note that the second and third steps in the original RSA paper use Euler's totient function \\(\\varphi(N)\\) instead of \\(\\lambda(N)\\). The relationship between these two functions is: \\[\\varphi(N)=\\lambda(N)⋅\\operatorname{gcd}(p-1,q-1)\\] Here \\(\\operatorname{gcd}\\) is the greatest common divisor function. Using \\(\\lambda(N)\\) can yield the minimum workable private exponent \\(d\\), which is conducive to efficient decryption and signature operations. Implementations that follow the above procedure, whether using Euler's or Carmichael's functions, are often referred to as \"textbook RSA \".

\n

Textbook RSA is insecure, and there are many simple and effective means of attack. Before discussing the security holes of the textbook RSA in detail, it is necessary to review the first known attack method - integer factorization!

\n

Integer Factorization

\n

The theoretical cornerstone of the security of the RSA encryption algorithm is the problem of factoring large numbers. If we can separate \\(p\\) and \\(q\\) from the known \\(N\\), we can immediately derive the private exponent \\(d\\) and thus completely crack RSA. Factoring large numbers is a presumed difficult computational problem. The best-known asymptotic running time algorithm is General Number Field Sieve, and its time complexity is \\({\\displaystyle \\exp \\left(\\left(c+o(1)\\right)(\\ln N)^{\\frac {1}{3}}(\\ln \\ln N)^{\\frac {2}{3}}\\right)}\\), where the constant \\(c = 4/\\sqrt[3]{9}\\)\\(\\displaystyle \\exp\\) and \\(\\displaystyle \\exp\\) is the exponential function of Euler's number (2.718).

\n

For a given large number, it is difficult to accurately estimate the actual complexity of applying the GNFS algorithm. However, based on the heuristic complexity empirical estimation, we can roughly see the increasing trend of computational time complexity:

\n
    \n
  • For a large number of 1024 bits, there are two prime factors of about 500 bits each, and the decomposition requires basic arithmetic operations of order \\(2^{70}\\)
  • \n
  • For a large number of 2048 bits, there are two prime factors of about 1000 bits each, and the decomposition requires basic arithmetic operations of order \\(2^{90}\\), a million times slower than the 1024-bit number
  • \n
\n

The rapid development of computer software and hardware technology has made many tasks that seemed impossible in the past become a reality. Check the latest record released by the RSA Factoring Challenge website. In February 2020, a team led by French computational mathematician Paul Zimmermann successfully decomposed the large number RSA-250 with 250 decimal digits (829 bits):

\n
RSA-250 = 6413528947707158027879019017057738908482501474294344720811685963202453234463
0238623598752668347708737661925585694639798853367
× 3337202759497815655622601060535511422794076034476755466678452098702384172921
0037080257448673296881877565718986258036932062711
\n

announcement

\n

According to the announcement of the factorization released by Zimmerman, using a 2.1GHz Intel Xeon Gold 6130 processor, the total computing time to complete this task is about 2700 CPU core-years. This number may seem large, but in today's era of cluster computing, grid computing, and cloud computing for the masses, it's not a stretch to think that organizations with strong financial backing can reduce computing time to hours or even minutes. As an example, go to the online tool website of the free open-source mathematical software system SageMath and enter the following first 5 lines of Sage Python code:

\n
p=random_prime(2**120)
q=random_prime(2**120)
n=p*q
print(n)
factor(n)
# The output
28912520751034191277571809785701738245635791077300278534278526509273423
38293227899687810929829874029597363 * 755029605411506802434801930237797621
\n

The result was obtained within minutes, and a large number of 72 decimal digits (240 bits) was decomposed. You know, in the 1977 RSA paper, it is mentioned that it takes about 104 days to decompose a 75-digit decimal number. The technological progress of mankind is so amazing!

\n

As the attacker's spear becomes sharper and sharper, the defender's shield must become thicker and thicker. Therefore, 1024-bit RSA is no longer secure, and applications should not use public key \\(N\\) values that are less than 2048 bits. And when high security is required, choose 4096-bit RSA.

\n

Elementary Attacks

\n

Although the decomposition of large numbers is an attack method known to everyone, the security vulnerabilities caused by some low-level errors commonly found in RSA applications make it possible to use simple attacks to succeed, and some typical ones are explained below.

\n
    \n
  • In the early development of RSA, finding large prime numbers took quite a bit of time based on the backward computing power of the time. Therefore, some system implementations tried to share the modulus \\(N\\). The idea was to generate only one set \\((p,q)\\), and then all users would use the same \\(N=pq\\) values, with a central authority that everyone trusted assigning key pairs \\((e_i,d_i)\\) to each user \\(i\\), and nothing would go wrong as long as the respective private keys \\(d_i\\) were kept. Unfortunately, this is a catastrophic mistake! This implementation has two huge security holes:

    \n
      \n
    1. The user \\(i\\) can decompose \\(N\\) using his own key pair \\((e_i,d_i)\\). Whether \\(d\\) is generated using the Euler function \\(\\varphi(N)\\) or the Carmichael function \\(\\lambda(N)\\), there are algorithms that quickly derive the prime factors \\(p\\) and \\(q\\) from a given \\(d\\) 2. And once \\(p\\) and \\(q\\) are known, user \\(i\\) can compute any other user's private key \\(d_j\\) with one's public key \\((N,e_j)\\). At this point, the other users have no secrets from user \\(i\\).

    2. \n
    3. Even if all users do not have the knowledge and skill to decompose \\(N\\), or are \"nice\" enough not to know the other users' private keys, a hacker can still perform a common modulus attack to break the users' messages. If the public keys of two users, Alice and Bob, are \\(e_1\\) and \\(e_2\\), and \\(e_1\\) and \\(e_2\\) happen to be mutually prime (which is very likely), then by Bézout's identity, the eavesdropper Eve can find that \\(s\\) and \\(t\\) satisfy: \\[e_{1}s+e_{2}t=gcd(e_1,e_2)=1\\] At this point, if someone sends the same message \\(m\\) to Alice and Bob, Eve can decrypt \\(m\\) after recording the two ciphertexts \\(c_1\\) and \\(c_2\\) and performing the following operation: \\[c_1^s⋅c_2^t\\equiv(m^{e _1})^s⋅(m^{e_2})^t\\equiv m^{e_{1}s+e_{2}t}\\equiv m\\pmod N\\] The corresponding Python function code is shown below.

      \n

      def common_modulus(e1, e2, N, c1, c2):
      # Call the extended Euclidean algorithm function
      g, s, t = gymp2.gcdext(e1, e2)
      assert g == 1
      if s < 0:
      # Find c1's modular multiplicative inverse\t\t re = int(gmpy2.invert(c1, N))
      c1 = pow(re, s*(-1), N)
      c2 = pow(c2, t, N)
      else:
      # t is negative, find c2's modular multiplicative inverse
      re = int(gmpy2.invert(c2, N))
      c2 = pow(re, t*(-1), N)
      c1 = pow(c1, a, N)
      return (c1*c2) % N
      Two library functions of gmpy23 are called here: gcdext() to implement the extended Euclidean algorithm, and invert() to find the modular multiplicative inverse element. Note that Python's exponential function pow() supports modular exponentiation, but the exponent must not be negative. Since one of \\(s\\) or \\(t\\) must be negative, you have to first call invert() to convert \\(c_1\\) or \\(c_2\\) to the corresponding modular multiplicative inverse, then invert the negative number to calculate the modular exponent. For example, lines 7 and 8 above implement \\(c_1^s=(c_1^{-1})^{-s}\\bmod N\\).

    4. \n
  • \n
  • Is it possible to reuse only \\(p\\) or \\(q\\) since the shared modulus \\(N\\) is proven to be insecure? This seems to avoid the common-modulus attack and ensure that each user's public key \\(N\\) value is unique. Big mistake! This is an even worse idea! The attacker gets the public \\(N\\) values of all users and simply combines \\((N_1,N_2)\\) pairwise to solve Euclid's algorithm for the great common divisor, and a successful solution gives a prime factor \\(p\\), and a simple division gives the other prime factor \\(q\\). With \\(p\\) and \\(q\\), the attacker can immediately compute the user's private key \\(d\\). This is the non-coprime modulus attack.

  • \n
  • When applying textbook RSA, if both the public exponent \\(e\\) and the plaintext \\(m\\) are small, such that \\(c=m^e<N\\), the plaintext \\(m\\) can be obtained by directly calculating the \\(e\\)th root of the ciphertext \\(c\\). Even if \\(m^e>N\\) but not large enough, then since \\(m^e=c+k⋅N\\), you can loop through the small \\(k\\) values to perform brute-force root extraction cracking. Here is the Python routine:

    \n

    def crack_small(c, e, N, repeat)
    times = 0
    msg = 0
    for k in range(repeat):
    m, is_exact = gmpy2.iroot(c + times, e)
    if is_exact and pow(m, e, N) == c:
    msg = int(m)
    break
    times += N
    return msg
    Here the gmpy2 library function iroot() is called to find the \\(e\\)th root.

  • \n
  • Textbook RSA is deterministic, meaning that the same plaintext \\(m\\) always generates the same ciphertext \\(c\\). This makes codebook attack possible: the attacker precomputes all or part of the \\(m\\to c\\) mapping table and saves, then simply searches the intercepted ciphertext for a match. Determinism also means that textbook RSA is not semantically secure and that the ciphertext can reveal some information about the plaintext. Repeated occurrences of the ciphertext indicate that the sender is sending the same message over and over again.

  • \n
  • Textbook RSA is malleable, where a particular form of algebraic operation is performed on the ciphertext and the result is reflected in the decrypted plaintext. For example, if there are two plaintexts \\(m_1\\) and \\(m_2\\), and encryption yields \\(c_1=m_1^e\\bmod N\\) and \\(c_2=m_2^e\\bmod N\\), what does \\((c_1⋅c_2)\\) decryption yield? Look at the following equation: \\[(c_1⋅c_2)^d\\equiv m_1^{ed}⋅m_2^{ed}\\equiv m_1⋅m_2\\pmod N\\] So the plaintext obtained after decrypting the product of the two ciphertexts is equal to the product of the two plaintexts. This feature is detrimental to RSA encryption systems in general and provides an opportunity for chosen-ciphertext attack. The following are two examples of attack scenarios:

    \n
      \n
    1. Imagine that there is an RSA decryption machine that can decrypt messages with an internally saved private key \\((N,d)\\). For security reasons, the decryptor will reject repeated input of the same ciphertext. An attacker, Marvin, finds a piece of ciphertext \\(c\\) that is rejected by the decryptor when he enters it directly because the ciphertext \\(c\\) has been decrypted before. Marvin finds a way to crack it. He prepares a plaintext \\(r\\) himself, encrypts it with the public key \\((N,e)\\) to generate a new ciphertext \\(c'={r^e}c\\bmod N\\), and then feeds the ciphertext \\(c'\\) to the decryptor. The decryption machine has not decrypted this new ciphertext, so it will not reject it. The result of the decryption is \\[m'\\equiv (c')^d\\equiv r^{ed}c^d\\equiv rm\\pmod N\\] Now that Marvin has \\(m'\\), he can calculate \\(m\\) using the formula \\(m\\equiv m'r^{-1}\\pmod N\\).

    2. \n
    3. Suppose Marvin wants Bob to sign a message \\(m\\), but Bob refuses to do so after reading the message content. Marvin can achieve his goal by using an attack called blinding4. He picks a random message \\(r\\), generates \\(m'={r^e}m\\bmod N\\), and then takes \\(m'\\) to Bob to sign. Bob probably thinks \\(m'\\) is irrelevant and signs it. The result of Bob's signature is \\(s'=(m')^d\\bmod N\\). Now Marvin has Bob's signature on the original message \\(m\\) using the formula \\(s=s'r^{-1}\\bmod N\\). Why? The reason is that \\[s^e\\equiv (s')^er^{-e}\\equiv (m')^{ed}r^{-e}\\equiv m'r^{-e}\\equiv m\\pmod N\\]

    4. \n
  • \n
\n

The above is by no means a complete list of elementary attack methods, but they are illustrative. In practical RSA applications, we must be very careful and should do the following:

\n
    \n
  • generate a unique public key modulus \\(N\\) for each user individually to prevent common-mode attacks
  • \n
  • not reuse the prime factor to generate the public key modulus \\(N\\), to eliminate the non-coprime modulus attack
  • \n
\n

For the textbook RSA deterministic and malleable flaws, and possible brute-force root extraction cracking vulnerabilities, the padding with random elements method can be used to protect against them, and the protection is valid due to the following:

\n
    \n
  • Padding ensures that the number of bits in the encrypted message is close to \\(N\\), while not using small \\(e\\) values, making possible brute-force root extraction cracking ineffective
  • \n
  • Random padding makes the same plaintext produce different ciphertexts, guaranteeing semantic security and making ciphertext attacks impossible
  • \n
  • Strictly format-defined padding destroys malleability and reduces the possibility of ciphertext selection attacks. For example, if the first few bytes after padding must be a given value, the decrypted data will most likely not conform to the predefined format after the algebraic operation on the corresponding ciphertext, which disrupts the ciphertext selection attack.
  • \n
\n

Low Public Exponent Attacks

\n

Using low public exponent is dangerous, and there are advanced attacks in the case of non-padding or improper padding, even if brute-force root extraction cracking does not succeed.

\n

Broadcast Attack

\n

Discovered by Swedish theoretical computer scientist Johan Håstad 5, hence the name Håstad's Broadcast Attack. Consider this simplified scenario, assuming that Alice needs to send the same message \\(m\\) to Bob, Carol, and Dave. The public keys of the three recipients are \\((N_1,3)\\), \\((N_2,3)\\), and \\((N_3,3)\\), i.e., the public exponent is all 3 and the public key modulus is different for each. The messages are not padded and Alice directly encrypts and sends three ciphertexts \\(c_1,c_2,c_3\\) using the public keys of the other three:

\n

\\[\\begin{cases}\nc_1=m^3\\bmod N_1\\\\\nc_2=m^3\\bmod N_2\\\\\nc_3=m^3\\bmod N_3\n\\end{cases}\\]

\n

At this point Eve secretly writes down the three ciphertexts, marking \\(M=m^3\\), and if she can recover \\(M\\), running a cube root naturally yields the plaintext \\(m\\). Obviously, the common modulus attack does not hold here, and we can also assume that the moduli are pairwise coprime, or else decomposing the modulus using the non-coprime modulus attack will work. So does Eve have a way to compute \\(M\\)? The answer is yes.

\n

In fact, the equivalent problem for solving \\(M\\) here is: Is there an efficient algorithm for solving a number that has known remainders of the Euclidean division by several integers, under the condition that the divisors are pairwise coprime? This efficient algorithm is Chinese Remainder Theorem!

\n

The Chinese remainder theorem gives the criterion that a system of one-element linear congruence equations has a solution and the method to solve it. For the following system of one-element linear congruence equations (be careful not to confuse it with the mathematical notation used to describe the attack scenario above):

\n

\\[(S) : \\quad \\left\\{ \n\\begin{matrix} x \\equiv a_1 \\pmod {m_1} \\\\\nx \\equiv a_2 \\pmod {m_2} \\\\\n\\vdots \\qquad\\qquad\\qquad \\\\\nx \\equiv a_n \\pmod {m_n} \\end\n{matrix} \\right.\\]

\n

Suppose that the integers \\(m_1,m_2,\\ldots,m_n\\) are pairwise coprime, then the system of equations \\((S)\\) has a solution for any integer \\(a_1,a_2,\\ldots,a_n\\) and the general solution can be constructed in four steps as follows:

\n

\\[\\begin{align}\nM &= m_1 \\times m_2 \\times \\cdots \\times m_n = \\prod_{i=1}^n m_i \\tag{1}\\label{eq1}\\\\\nM_i &= M/m_i, \\; \\; \\forall i \\in \\{1, 2, \\cdots , n\\}\\tag{2}\\label{eq2}\\\\\nt_i M_i &\\equiv 1\\pmod {m_i}, \\; \\; \\forall i \\in \\{1, 2, \\cdots , n\\}\\tag{3}\\label{eq3}\\\\\nx &=kM+\\sum_{i=1}^n a_i t_i M_i\\tag{4}\\label{eq4}\n\\end{align}\\]

\n

The last line above, Eq. (4) gives the formula of the general solution. In the sense of modulus \\(M\\), the unique solution is \\(\\sum_{i=1}^n a_i t_i M_i \\bmod M\\).

\n

Try to solve the things whose number is unknown problem at the beginning of this article by using the Chinese remainder theorem

\n
\n

First, correspond the variable symbols to the values: \\[m_1=3,a_1=2;\\quad m_2=5,a_2=3;\\quad m_3=7,a_3=2\\] Then calculate \\(M=3\\times5\\times7=105\\), which in turn leads to the derivation of: \\[\\begin{align}\nM_1 &=M/m_1=105/3=35,\\quad t_1=35^{-1}\\bmod 3 = 2\\\\\nM_2 &=M/m_2=105/5=21,\\quad t_2=21^{-1}\\bmod 5 = 1\\\\\nM_3 &=M/m_3=105/7=15,\\quad t_3=15^{-1}\\bmod 7 = 1\\\\\n\\end{align}\\] Finally, take these into the general solution formula: \\[x=k⋅105+(2⋅35⋅2+3⋅21⋅1+2⋅15⋅1)=k⋅105+233\\] So the smallest positive integer solution concerning modulus 105 is \\(233\\bmod 105=23\\)

\n

In his mathematical text \"Suanfa Tongzong\", Cheng Dawei, a mathematician of the Ming Dynasty in the 16th century, compiled the solutions recorded by the mathematician Qin Jiushao of the Song Dynasty in the \"Mathematical Treatise in Nine Sections\" into a catchy \"Sun Tzu's Song\":

\n
\n

Three friends set out with seventy rare
\nTwenty-one blossoms on five trees of plums
\nSeven men reunited at the half-month
\nAll be known once divided by one hundred and five

\n
\n

Here we must admire the wisdom of the ancient Chinese who, in the absence of a modern mathematical symbol system, were able to derive and summarize such an ingenious solution, contributing an important mathematical theorem to mankind.

\n\n
\n

So Eve just applies the solution of the Chinese Remainder Theorem, computes \\(M\\), and then finds its cube root to get the plaintext \\(m\\), and the attack succeeds. More generally, setting the number of receivers to \\(k\\), if all receivers use the same \\(e\\), then this broadcast attack is feasible as long as \\(k\\ge e\\).

\n

Håstad further proves that even if padding is used to prevent broadcast attacks, if the messages generated by the padding scheme are linearly related to each other, such as using the formula \\(m_i=i2^b+m\\) (\\(b\\) is the number of bits of \\(m\\)) to generate the message sent to the receiver \\(i\\), then the broadcast attack can still recover the plaintext \\(m\\) as long as \\(k>e\\). The broadcast attack in this case is still based on the Chinese remainder theorem, but the specific cracking method depends on the information of the linear relationship.

\n

To summarize the above analysis, to prevent the broadcast attack, we must use a higher public exponent \\(e\\) and apply random padding at the same time. Nowadays, the common public key exponent \\(e\\) is 65537 (\\(2^{16}+1\\)), which can balance the efficiency and security of message encryption or signature verification operations.

\n

Last, Python routines for simulating broadcast attacks are given as follows:

\n
def solve_crt(ai: list, mi: list):
'''mi and ai are the list of modulus and remainders.
The precondition of the function is that the modulus
in the mi list are pairwise coprime.'''
M = reduce(lambda x, y: x * y, mi)
ti = [a * (M//m) * int(gmpy2.invert(M//m, m)) for (m, a) in zip(mi, ai)]
return reduce(lambda x, y: x + y, ti) % M

def rsa_broadcast_attack(ctexts: list, moduli: list):
'''RSA broadcast attack: applying CRT to crack e=3'''
c0, c1, c2 = ctexts[0], ctexts[1], ctexts[2]
n0, n1, n2 = moduli[0], moduli[1], moduli[2]
m0, m1, m2 = n1 * n2, n0 * n2, n0 * n1
t0 = (c0 * m0 * int(gmpy2.invert(m0, n0)))
t1 = (c1 * m1 * int(gmpy2.invert(m1, n1)))
t2 = (c2 * m2 * int(gmpy2.invert(m2, n2)))
c = (t0 + t1 + t2) % (n0 * n1 * n2)
return int(gmpy2.iroot(c, 3)[0])

def uint_to_bytes(x: int) -> bytes:
'''convert unsigned integer to byte array'''
if x == 0:
return bytes(1)
return x.to_bytes((x.bit_length() + 7) // 8, 'big')

quote = b'The cosmos is within us. We are made of star stuff. - Carl Sagan'
bob = RSA(1024, 3)
carol = RSA(1024, 3)
dave = RSA(1024, 3)
cipher_list = [bob.encrypt(quote), carol.encrypt(quote), dave.encrypt(quote)]
modulus_list = [bob.n, carol.n, dave.n]

cracked_cipher = solve_crt(cipher_list, modulus_list)
cracked_int = int(gmpy2.iroot(cracked_cipher, 3)[0])
assert cracked_int == rsa_broadcast_attack(cipher_list, modulus_list)

hacked_quote = uint_to_bytes(cracked_int)
assert hacked_quote == quote
\n

This code uses two methods to simulate the broadcast attack. One calls the generic Chinese remainder theorem solver function solve_crt() and then gets the cube root of the result; the other calls the special broadcast attack function rsa_broadcast_attack() for the public key index \\(e=3\\), which directly outputs the cracked plaintext value. The internal implementation of these two functions is based on the generalized formula of the Chinese remainder theorem, and the output results should be identical. The cracked plaintext value is then input to the uint_to_bytes() function, which is converted into a byte array to compare with the original quote. Note that the program uses objects generated by the RSA class to simulate the receivers Bob, Carroll, and Dave, and the implementation of the RSA class is omitted here given the limitation of space.

\n
\n

Next article: RSA: Attack and Defense (II)

\n
\n
\n
\n
    \n
  1. American computer scientist and security expert Gary McGraw has a famous piece of advice for software developers - \"never roll your own cryptography\"↩︎

  2. \n
  3. The original RSA paper (Part IX, Section C) did mention Miller's algorithm for factoring \\(N\\) with a known \\(d\\). This algorithm also applies to \\(d\\) generated by the Carmichael function \\(\\lambda(N)\\).↩︎

  4. \n
  5. gmpy2 is a Python extension module written in C that supports multi-precision arithmetic.↩︎

  6. \n
  7. On some special occasions, blinding can be used for effective privacy protection. For example, in cryptographic election systems and digital cash applications, the signer and the message author can be different.↩︎

  8. \n
  9. Johan Håstad, a Swedish theoretical computer scientist, a professor at the KTH Royal Institute of Technology, and a Fellow of the American Mathematical Society (AMS) and an Association for Computing Machinery (ACM) fellow.↩︎

  10. \n
\n
\n","categories":["Technical Know-how"],"tags":["Cryptography","Network Security","Python Programming"]},{"title":"Please Stop Using TLS 1.0 and TLS 1.1 Now!","url":"/en/2022/11/10/Stop-TLS1-0-TLS1-1/","content":"

In March 2021, the Internet Engineering Task Force (IETF) released RFC 8996, classified as a current best practice, officially announcing the deprecation of the TLS 1.0 and TLS 1.1 protocols. If your applications and web services are still using these protocols, please stop immediately and update to TLS 1.2 or TLS 1.3 protocol versions as soon as possible to eliminate any possible security risks.

\n

One single vulnerability is all an attacker needs.
Window Snyder (American computer security expert, former Senior Security Strategist at Microsoft, and has been a top security officer at Apple, Intel and other companies)

\n
\n

RFC Interpretation

\n

The document title of RFC 8996 is quite straightforward, \"Deprecating TLS 1.0 and TLS 1.1\". So what is the rationale it gives? Here is a simple interpretation.

\n

First, take a look at its abstract:

\n
\n

This document formally deprecates Transport Layer Security (TLS) versions 1.0 (RFC 2246) and 1.1 (RFC 4346). Accordingly, those documents have been moved to Historic status. These versions lack support for current and recommended cryptographic algorithms and mechanisms, and various government and industry profiles of applications using TLS now mandate avoiding these old TLS versions. TLS version 1.2 became the recommended version for IETF protocols in 2008 (subsequently being obsoleted by TLS version 1.3 in 2018), providing sufficient time to transition away from older versions. Removing support for older versions from implementations reduces the attack surface, reduces opportunity for misconfiguration, and streamlines library and product maintenance.

\n

This document also deprecates Datagram TLS (DTLS) version 1.0 (RFC 4347) but not DTLS version 1.2, and there is no DTLS version 1.1.

\n

This document updates many RFCs that normatively refer to TLS version 1.0 or TLS version 1.1, as described herein. This document also updates the best practices for TLS usage in RFC 7525; hence, it is part of BCP 195.

\n
\n

The information given here is clear, the reasons for deprecating them are purely technical. TLS 1.0 and TLS 1.1 cannot support stronger encryption algorithms and mechanisms, and cannot meet the high-security requirements of various network applications in the new era. TLS is TCP-based. Corresponding to the UDP-based DTLS protocol, RFC 8996 also announced the deprecation of the DTLS 1.0 protocol.

\n

The Introduction section lists some details of the technical reasons:

\n
    \n
  1. They require the implementation of older cipher suites that are no longer desirable for cryptographic reasons, e.g., TLS 1.0 makes TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA mandatory to implement.
  2. \n
  3. There is a lack of support for current recommended cipher suites, especially authenticated encryption with associated Data (AEAD), which were not supported prior to TLS 1.2.
  4. \n
  5. The integrity of the handshake depends on SHA-1 hash.
  6. \n
  7. The authentication of the peers depends on SHA-1 signatures.
  8. \n
  9. Support for four TLS protocol versions increases the likelihood of misconfiguration.
  10. \n
  11. At least one widely used library has plans to drop TLS 1.1 and TLS 1.0 support in upcoming releases.
  12. \n
\n

Clauses 5 and 6 above are clear and need no further explanation.

\n

For 3DES mentioned in Clause 1, although it uses three independent keys with a total length of 168 bits, considering the possible meet-in-the-middle_attack attack, its effective key strength is only 112 bits. Also, the 3DES encryption block length is still 64 bits, which makes it extremely vulnerable to birthday attack (see Sweet32). NIST stipulates that a single 3DES key group can only be used for encrypting \\(2^{20}\\) data blocks (ie 8MB). This was of course too small, and eventually, NIST decided in 2017 to deprecate 3DES in the IPSec and TLS protocols.

\n

3DES is just one example, another category that has been phased out earlier is cipher suites that use RC4 stream ciphers, see RFC 7465 for details. In addition, there are various problems in the implementation of block cipher CBC mode, which are often exploited by attackers to crack TLS sessions. A summary of various attacks and countermeasures of TLS 1.0 and TLS 1.1 is described in detail in NIST800-52r2 and RFC7457. These two reference documents provide the key rationale for deprecation. Obviously, any protocol that mandates the implementation of insecure cipher suites should be on the list to be eliminated.

\n

In the second section of the document, the content in Section 1.1 \"The History of TLS\" of NIST800-52r2 is directly quoted (abbreviated as shown in the following table):

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
TLS VersionProtocol DocumentKey Feature Update
1.1RFC 4346Improved initialization vector selection and padding error processing to address weaknesses discovered on the CBC mode of operation defined in TLS 1.0.
1.2RFC 5246Enhanced encryption algorithms, particularly in the area of hash functions, can support SHA-2 series algorithms for hashing, MAC, and pseudorandom function computations, also added AEAD cipher suite.
1.3RFC 8446A significant change to TLS that aims to address threats that have arisen over the years. Among the changes are a new handshake protocol, a new key derivation process that uses the HMAC-based Extract-and-Expand Key Derivation Function (HKDF), and the removal of cipher suites that use RSA key transport or static Diffie-Hellman key exchanges, the CBC mode of operation, or SHA-1.
\n

AEAD is an encryption mode that can guarantee the confidentiality, integrity, and authenticity of data at the same time, typically such as CCM and GCM. TLS 1.2 introduced a range of AEAD cipher suites, and its high security made it the exclusive choice for TLS 1.3. These annotate Clause 2 of technical reasons.

\n

Clauses 3 and 4 of technical reasons call out SHA-1, so what is the problem with SHA-1? Section 3 of the document cites a paper by two French researchers, Karthikeyan Bhargavan and Gaetan Leurent .

\n

As a cryptographic hash function, SHA-1 was designed by the National Security Agency (NSA) and then published as a Federal Information Processing Standard (FIPS) by the National Institute of Standards and Technology (NIST). SHA-1 can process a message up to \\(2^{64}\\) bits and generate a 160-bit (20-byte) hash value known as the message digest. Therefore, the complexity of brute force cracking based on birthday attack is \\(2^{80}\\) operations. In 2005, Chinese cryptographer Wang Xiaoyun and her research team made a breakthrough in this field. The high-efficiency SHA-1 attack method they published can be used to find a hash collision within a computational complexity of \\(2^{63}\\). This has brought a huge impact on the security of SHA-1, but it does not mean that the cracking method can enter the practical stage.

\n

Network security protocols (such as TLS, IKE, and SSH, etc.) rely on the second preimage resistance of cryptographic hash functions, that is, it is computationally impossible to find any secondary input value that has the same output as a specific input value. For example, for a cryptographic hash function \\(h(x)\\) and given input \\(x\\), it is difficult to find a sub-preimage \\(x^′ ≠ x\\) that is satisfying \\(h(x) = h(x^′)\\). Because finding a hash collision does not mean that a sub-preimage can be located, in practice, it was once thought that continuing to use SHA-1 is not a problem.

\n

However, in 2016, Bhargavan and Leurent (who implemented the aforementioned Sweet32 attack against 64-bit block ciphers) discovered a new class of methods to attack key exchange protocols that shattered this perception. These methods are based on the principle of the chosen prefix collision attack. That is, given two different prefixes \\(p_1\\) and \\(p_2\\), the attack finds two appendages \\(m_1\\) and \\(m_2\\) such that \\(h(p_1 ∥ m_1) = hash(p_2 ∥ m_2)\\). Using this approach, they demonstrated a man-in-the-middle attack against TLS clients and servers to steal sensitive data, and also showed that the attack could be used to masquerade and downgrade during TLS 1.1, IKEv2, and SSH-2 session handshakes. In particular, they proved that with only \\(2^{77}\\) operations the handshake protocol using SHA-1 or MD5 and SHA-1 concatenated hash values ​​could be cracked.

\n

Since neither TLS 1.0 nor TLS 1.1 allows the peers to choose a stronger cryptographic hash function for signatures in the ServerKeyExchange or CertificateVerify messages, the IETF confirmed that using a newer protocol version is the only upgrade path.

\n

Sections 4 and 5 of the document again clarify that TLS 1.0 and TLS 1.1 must not be used, and negotiation to TLS 1.0 or TLS 1.1 from any TLS version is not allowed. This means that ClientHello.client_version and ServerHello.server_version issued by the TLS client and server, respectively, must not be {03,01} (TLS 1.0) or {03,02} (TLS 1.1). If the protocol version number in the Hello message sent by the other party is {03,01} or {03,02}, the local must respond with a \"protocol_version\" alert message and close the connection.

\n

It is worth noting that due to historical reasons, the TLS specification does not specify the value of the record layer version number (TLSPlaintext.version) when the client sends the ClientHello message. So to maximize interoperability, TLS servers MUST accept any value {03,XX} (including {03,00}) as the record layer version number for ClientHello messages, but they MUST NOT negotiate TLS 1.0 or 1.1.

\n

Section 6 of the document declares a textual revision to the previously published RFC 7525 (Recommendations for the Secure Use of TLS and DTLS). Three places in this RFC change implementation-time negotiations of TLS 1.0, TLS 1.1, and DTLS 1.0 from \"SHOULD NOT\" to \"MUST NOT\". The last section is a summary of standard RFC operations and security considerations.

\n

Industry Responses

\n

In the industry of large public online services, GitHub was the first to act. They started disabling TLS 1.0 and TLS 1.1 in all HTTPS connections back in February 2018, while also phasing out insecure diffie-hellman-group1-sha1 and diffie-hellman-group14-sha1 key exchange algorithms in the SSH connection service. In August 2018, Eric Rescorla, CTO of Mozilla Firefox, published the TLS 1.3 technical specification RFC 8446. Two months later, Mozilla issued a statement together with the three giants of Apple, Google, and Microsoft, and put the deprecation of TLS 1.0 and TLS 1.1 on the agenda.

\n

The following is a brief summary of the actions of several related well-known companies:

\n
    \n
  • Microsoft: For Office 365 services, TLS 1.0 and 1.1 disabling for commercial customers was temporarily suspended due to COVID-19. The mandatory rollout of TLS 1.2 was restarted on October 15, 2020. Users of SharePoint and OneDrive will need to update and configure .NET to support TLS 1.2. Users of Teams Rooms recommend upgrading the app to version 4.0.64.0. The Surface Hub released support for TLS 1.2 in May 2019. The Edge browser version 84 does not use TLS 1.0/1.1 by default, while the Azure cloud computing service will permanently obsolete TLS 1.0/1.1 from March 31, 2022.
  • \n
  • Google: As early as 2018, TLS 1.3 was added to Chrome 70. Starting with Chrome 84, support for TLS 1.0 and TLS 1.1 is completely removed. After running TLS 1.3 in Search Engine, Gmail, YouTube, and various other Google services for some time, TLS 1.3 was officially rolled out in 2020 as the default configuration for all new and existing Cloud CDN and Global Load Balancing customers.
  • \n
  • Apple: Announced in September 2021 that TLS 1.0 and TLS 1.1 will be deprecated in iOS 15, iPadOS 15, macOS 12, watchOS 8, and tvOS 15, and support for them be completely removed in future releases. If the user's application activates the App Transport Security (ATS) feature on all connections, no changes are required. Users are also notified to ensure that the web server supports newer TLS versions and to remove the following deprecated Security.framework symbols from the app\n
      \n
    • tls_protocol_version_t.TLSv10
    • \n
    • tls_protocol_version_t.TLSv11
    • \n
    • tls_protocol_version_t.DTLSv10
    • \n
  • \n
  • Mozilla: Starting with Firefox version 78, the minimum TLS version configured by default is TLS 1.2. In early 2020, Mozilla briefly removed TLS 1.0 and TLS 1.1 from Firefox completely, but this caused many users to be unable to open some COVID-19 outbreak public information sites, so the related functionality had to be restored. Following this, Mozilla provides helpful information on its technical support page, instructing users to modify the minimum TLS version number in the default configuration as needed.
  • \n
  • Cisco: The Cisco Umbrella (renamed from OpenDNS) service discontinued support for all versions of TLS prior to 1.2 on March 31, 2020. After this, only TLS 1.2 compliant clients will be able to connect. In the router and switch product lines, web management has basically been implemented around 2020 to allow only TLS 1.2 or subsequent versions.\n
      \n
    • The CAPWAP connection between Cisco's Wireless Access Point (AP) and Wireless LAN Controller (WLC) is established over DTLS. All 802.11ac Wave 2 and 802.11ax APs from 2015 to the most recent release support DTLS 1.2. The AireOS WLC added DTLS 1.2 functionality in version 8.3.11x.0, and the next-generation C9800 WLC running IOS-XE supports DTLS 1.2 from the start. Note that because of the large number of existing network deployments using older equipment and software versions, DTLS 1.0 support cannot be removed immediately from APs and WLCs at this time to protect user investments. However, DTLS 1.2 is already the default optimal choice for APs and WLCs.
    • \n
  • \n
\n

Protocol Test

\n

Both TLS/DTLS clients and servers need to be tested to verify that their implementations follow the current best practices of RFC 8996.

\n

SSL Lab Test

\n

Qualys originated as a non-commercial SSL Labs Projects. They offer a free and simple client and server testing service, as well as a monitoring panel reporting TLS/SSL security scan statistics for the most popular Internet sites. Below is the most recent chart of protocol support statistics for November 2022.

\n

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Protocol VersionSecuritySupporting Sites (Oct. 2022)Supporting Site (Nov. 2022)% Change
SSL 2.0Insecure316(0.2%)303(0.2%)-0.0%
SSL 3.0Insecure3,015(2.2%)2,930(2.2%)-0.0%
TLS 1.0Deprecated47,450(34.9%)46,691(34.4)-0.5%
TLS 1.1Deprecated51,674(38.1%)50,816(37.5%)-0.6%
TLS 1.2Depending on the Cipher Suite and the Client135,557(99.8)135,445(99.9)+0.1%
TLS 1.3Secure78,479(57.8%)79,163(58.4%)+0.6%
\n

As you can see, almost 100% of sites are running TLS 1.2, and the percentage of TLS 1.3 support is close to 60%. This is very encouraging data. While very few sites are still running SSL 2.0/3.0 and TLS 1.0/1.1 are both still supported at around 35%, overall their percentages are continuing to decline and this good trend should continue.

\n

This blog site is served by GitHub Page, enter the URL to SSL Server Test page and submit it to get a summary of the test results as follows.

\n

\n

The site achieved the highest overall security rating of A+. It got a perfect score for certificate and protocol support, and a 90 for both key exchange and password strength. This shows that GitHub fulfills its security promises to users and deserves the trust of programmers.

\n

The configuration section of the report gives details of the test results for protocol support and cipher suites as follows.

\n

\n

This further confirms that the GitHub Page only supports TLS 1.2/1.3, as required by RFC 8996. It can also be seen that under the \"Cipher Suites\" subheading, TLS 1.3 shows two GCMs and one ChaCha20-Poly1305, which are all cipher suites based on the AEAD algorithms. Three cipher suites of the same type are the preferred TLS 1.2 cipher suites for the server as well. This is exactly the current commonly adopted configuration of secure cryptographic algorithms.

\n

User Selftest

\n

If you suspect that a private server is still using the outdated TLS/SSL protocol, you can do a simple test with the command line tool curl, an example of which is as follows.

\n
❯ curl https://www.cisco.com -svo /dev/null --tls-max 1.1
* Trying 104.108.67.95:443...
* Connected to www.cisco.com (104.108.67.95) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
} [151 bytes data]
* error:1404B42E:SSL routines:ST_CONNECT:tlsv1 alert protocol version
* Closing connection 0
\n

Here enter the command line option -tls-max 1.1 to set the highest protocol version 1.1 and connect to the Cisco home page. The output shows that the connection failed and that a \"protocol version\" alert message was received. This indicates that the server has rejected the TLS 1.1 connection request, and the response is exactly what is required by RFC 8996.

\n

The openssl command line tool provided by the general purpose open source cryptography and secure communication toolkit OpenSSL can also do the same test. To test whether the server supports the TLS 1.2 protocol, use the option s_client to emulate a TLS/SSL client and also enter -tls1_2 to specify that only TLS 1.2 is used. The command line runs as follows.

\n
❯ openssl s_client -connect www.cisco.com:443 -tls1_2
CONNECTED(00000005)
depth=2 C = US, O = IdenTrust, CN = IdenTrust Commercial Root CA 1
verify return:1
depth=1 C = US, O = IdenTrust, OU = HydrantID Trusted Certificate Service, CN = HydrantID Server CA O1
verify return:1
depth=0 CN = www.cisco.com, O = Cisco Systems Inc., L = San Jose, ST = California, C = US
verify return:1
---
Certificate chain
0 s:/CN=www.cisco.com/O=Cisco Systems Inc./L=San Jose/ST=California/C=US
i:/C=US/O=IdenTrust/OU=HydrantID Trusted Certificate Service/CN=HydrantID Server CA O1
1 s:/C=US/O=IdenTrust/OU=HydrantID Trusted Certificate Service/CN=HydrantID Server CA O1
i:/C=US/O=IdenTrust/CN=IdenTrust Commercial Root CA 1
2 s:/C=US/O=IdenTrust/CN=IdenTrust Commercial Root CA 1
i:/C=US/O=IdenTrust/CN=IdenTrust Commercial Root CA 1
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIHrzCCBpegAwIBAgIQQAF9KqwAKOKNhDf17h+WazANBgkqhkiG9w0BAQsFADBy
...
4TY7
-----END CERTIFICATE-----
subject=/CN=www.cisco.com/O=Cisco Systems Inc./L=San Jose/ST=California/C=US
issuer=/C=US/O=IdenTrust/OU=HydrantID Trusted Certificate Service/CN=HydrantID Server CA O1
---
No client certificate CA names sent
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 5765 bytes and written 322 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: 1656D7D14447C1D5E68943F614A697455E60A036957D8D8C18F3B198DF42969F
Session-ID-ctx:
Master-Key: BB1209155344C55792077A4337964661FCA4F3F5BBF3185112F5E235BD07AD63838D24F5CF97161E696CB57398CAF478
TLS session ticket lifetime hint: 83100 (seconds)
TLS session ticket:
0000 - 00 00 0b 33 d4 56 15 3d-64 e8 fa 1d cf c1 1c 04 ...3.V.=d.......
...
0090 - 1b 96 9c 25 82 70 a8 ed-24 1d 70 c9 28 56 84 59 ...%.p..$.p.(V.Y

Start Time: 1653265585
Timeout : 7200 (sec)
Verify return code: 0 (ok)
---
\n

This record is very detailed and the format is very readable. From the output, it can be understood that the digital certificate of the Cisco home page server is digitally signed and certified by the root certificate authority IdenTrust. The client-server session is built on the TLS 1.2 protocol, and the selected cipher suite is ECDHE-RSA-AES128-GCM-SHA256 of type AEAD, which is identical to the preferences provided by the GitHub Page.

\n

Browser Test

\n

If you are not sure about the security of your browser and want to test whether it still supports the pre-TLS 1.2 protocols, you can enter the following URL in your browser's address bar.

\n\n

After connecting to the second URL with the default configuration of Firefox, the page shows the following

\n
\n

Secure Connection Failed

\n

An error occurred during a connection to tls-v1-1.badssl.com:1011. Peer using unsupported version of security protocol.

\n

Error code: SSL_ERROR_UNSUPPORTED_VERSION

\n
    \n
  • The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.
  • \n
  • Please contact the website owners to inform them of this problem.
  • \n
\n

This website might not support the TLS 1.2 protocol, which is the minimum version supported by Firefox.

\n
\n

This error message clearly indicates that Firefox is running a minimum TLS protocol version of 1.2 in this configuration, and since the other side is only running TLS 1.1, the two sides cannot establish a connection.

\n

So what is the result of the connection when the browser does still retain TLS 1.0/1.1 functionality?

\n

For testing purposes, you can first change the default TLS preference value of Firefox to 1.1 by following the steps below (refer to the figure below).

\n
    \n
  1. Open a new tab, type about:config in the address bar, and press Enter/Return.
  2. \n
  3. The page prompts \"Proceed with Caution\", click the Accept the Risk and Continue button.
  4. \n
  5. In the search box at the top of the page, type TLS to display the filtered list.
  6. \n
  7. Find the security.tls.version.min preference option and click the Edit icon to change the minimum TLS version.\n
      \n
    • TLS 1.0 => 1
    • \n
    • TLS 1.1 => 2
    • \n
    • TLS 1.2 => 3
    • \n
    • TLS 1.3 => 4
    • \n
  8. \n
\n

\n

At this point, then connect to https://tls-v1-1.badssl.com, the result is

\n

\n

This bold red page tells you that the browser you are currently using does not have TLS 1.1 disabled and is a security risk, so try not to use it if you can.

\n

After testing, don't forget to restore the default TLS minimum version setting (3) for Firefox.

\n

References

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

Besides NIST and RFC documents, For an in-depth study of the TLS protocol specification, system implementation, and application deployment, a careful reading of the following three books is recommended.

\n
\n
    \n
  1. SSL and TLS: Theory and Practice, Second Edition (2016) - This book provides a comprehensive discussion of the SSL, TLS, and DTLS protocols. It has complete details on the theory and practice of the protocols, offering readers a solid understanding of their design principles and modes of operation. The book also presents the advantages and disadvantages of the protocols compared to other Internet security protocols and provides the details necessary to correctly implement the protocols while saving time on the security practitioner’s side.
  2. \n
  3. Implementing SSL/TLS Using Cryptography and PKI (2011) - For a network professional who knows C programming, this book is a hands-on, practical guide to implementing SSL and TLS protocols for Internet security. Focused on how to implement SSL and TLS, it walks you through all the necessary steps, whether or not you have a working knowledge of cryptography. The book covers TLS 1.2, including implementations of the relevant cryptographic protocols, secure hashing, certificate parsing, certificate generation, and more.
  4. \n
  5. Bulletproof TLS and PKI, Second Edition: Understanding and Deploying SSL/TLS and PKI to Secure Servers and Web (2022) - This book is a complete guide to using TLS encryption and PKI to deploy secure servers and web applications. Written by Ivan Ristić, founder of the popular SSL Labs website, it will teach you everything you need to know to protect your systems from eavesdropping and impersonation attacks. You can also find just the right mix of theory, protocol detail, vulnerability and weakness information, and deployment advice to get the work done.
  6. \n
\n","categories":["Technology Review"],"tags":["Cryptography","Network Security"]},{"title":"TLS 1.3 and the Coming NIST Mandate","url":"/en/2023/08/21/TLS1-3-intro/","content":"

TLS (Transport Layer Security) is a cryptographic protocol to secure network communication. TLS 1.3 is the latest version of the TLS protocol, succeeding TLS 1.2. TLS 1.3 aims to provide more robust security, higher privacy protection, as well as better performance than previous versions. Here is a brief introduction to TLS 1.3. Also, we discuss NIST's requirement for TLS 1.3 readiness and give examples of enabling TLS 1.3 in some commonly used web servers.

\n

It takes 20 years to build a reputation and a few minutes of cyber-incident to ruin it.
Stéphane Nappo (Vice President and Global Chief Information Security Officer of Groupe SEB, France, 2018 Global CISO of the year)

\n
\n

Introduction to TLS 1.3

\n

TLS 1.3 is the latest recommended cryptographic protocol for protecting a wide variety of network communications, including web browsing, email, online trading, instant messaging, mobile payments, and many other applications. By using TLS 1.3, more secure and reliable communication connections can be established, ensuring confidentiality, authenticity, and data integrity. It was standardized by the Internet Engineering Task Force (IETF) in August 2018, and published as RFC 8446.

\n

TLS 1.3 introduces some important improvements over TLS 1.2. The table below presents a quick comparison of the two:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
AspectTLS 1.2TLS 1.3
Protocol DesignRequest-response modelReduced round trips
HandshakeMultiple round tripsSingle round trip
Cipher SuitesSupports wide range, including insecure onesFocuses on stronger algorithms
SecurityKnown vulnerabilities, e.g., CBC vulnerabilitiesAddresses previous issues, stronger security
PerformanceHigher latency due to more round tripsFaster connection establishment
Resilience to AttacksVulnerable to downgrade attacks and padding oracle attacksAdditional protections against attacks
CompatibilityWidely supported across platformsIncreasing support, may not be available on older systems
Implementation SupportsAvailable in many cryptographic librariesSupported in various libraries
\n

It can be seen that enhanced security and performance improvements are the most notable features of TLS 1.3, and we can explore more into these in the following sections.

\n

Security Hardening

\n

Cipher Suites

\n

The protocol design principle of TLS 1.3 has enhanced security as its primary goal. As a result, TLS 1.3 drastically reduces the number of supported cipher suites. It removes insecure and weak cipher suites, leaving only more secure and modern cipher suites. This helps to increase the security of communications and avoids the use of outdated or vulnerable cipher suites.

\n

Specifically, TLS 1.3 removes various cipher suites that use static RSA key transport, static Diffie-Hellman key exchange, CBC mode of operation, or SHA-1. It adopts only a limited number of Authenticated Encryption with Associated Data (AEAD) cipher suites. AEAD can guarantee the confidentiality, integrity, and authenticity of data at the same time, and its high security makes it the exclusive choice for TLS 1.3.

\n

On the other hand, the name string of the cipher suite used in previous TLS versions included all algorithms for key exchange, digital signatures, encryption, and message authentication. Each cipher suite is assigned a 2-byte code point in the TLS Cipher Suites registry managed by the Internet Assigned Numbers Authority (IANA). Every time a new cryptographic algorithm is introduced, a series of new combinations need to be added to the list. This has led to an explosion of code points representing every valid choice of these parameters. This situation also makes the selection of cipher suites complicated and confusing.

\n

The design of TLS 1.3 changed the concept of the cipher suite. It separates the authentication and key exchange mechanisms from the record protection algorithm (including secret key length) and a hash to be used with both the key derivation function and handshake message authentication code (MAC). The new cipher suite naming convention is TLS_<AEAD>_<Hash>, where the hash algorithm is used for the newly defined key derivation function HKDF of TLS 1.3 and the MAC generation in the handshake phase. The cipher suites defined by the TLS 1.3 protocol are:

\n
RFC 8446 - Appendix B.4. Cipher Suites
+------------------------------+-------------+
| Description | Value |
+------------------------------+-------------+
| TLS_AES_128_GCM_SHA256 | {0x13,0x01} |
| | |
| TLS_AES_256_GCM_SHA384 | {0x13,0x02} |
| | |
| TLS_CHACHA20_POLY1305_SHA256 | {0x13,0x03} |
| | |
| TLS_AES_128_CCM_SHA256 | {0x13,0x04} |
| | |
| TLS_AES_128_CCM_8_SHA256 | {0x13,0x05} |
+------------------------------+-------------+
\n

This simplified cipher suite definition and greatly reduced set of negotiation parameters also speed up TLS 1.3 handshake, improving overall performance.

\n

Key Exchange

\n

TLS 1.3 emphasizes forward secrecy, ensuring that the confidentiality of communications is protected even if long-term secrets used in the session key exchange are compromised. It only allows key exchange based on ephemeral Diffie-Hellman key exchange (DHE) or ephemeral elliptic curve Diffie-Hellman key exchange (ECDHE). Both have the property of forward secrecy. Also, the protocol explicitly restricts the use of secure elliptic curve groups and finite field groups for key exchange:

\n
/* Elliptic Curve Groups (ECDHE) */
secp256r1(0x0017), secp384r1(0x0018), secp521r1(0x0019),
x25519(0x001D), x448(0x001E),

/* Finite Field Groups (DHE) */
ffdhe2048(0x0100), ffdhe3072(0x0101), ffdhe4096(0x0102),
ffdhe6144(0x0103), ffdhe8192(0x0104),
\n

The above elliptic curve groups for ECDHE are specified by RFC 8422. The first three are defined by the FIPS.186-4 specification and the corresponding NIST names are P-256/P-384/P-512, while the next two (x25519/x448) are recommended by ANSI.X9-62.2005. RFC 7919 specifies four finite field groups (ffdhe####) for DHE. The primes in these finite field groups are all safe primes.

\n

In number theory, a prime number \\(p\\) is a safe prime if \\((p-1)/2\\) is also prime.

\n
\n

Signature Verification

\n

For signature verification in the key exchange phase, TLS 1.3 introduces more signature algorithms to meet different security requirements:

\n
    \n
  • RSA signature algorithm: TLS 1.3 still supports RSA-based signature algorithms, including RSA-PKCS1-SHA256, RSA-PKCS1-SHA384, etc. These algorithms use RSA keys for digital signatures.
  • \n
  • ECDSA signature algorithm: TLS 1.3 introduces more signature algorithms based on elliptic curve cryptography (ECC), such as ECDSA-SHA256, ECDSA-SHA384, etc. These algorithms use elliptic curve keys for digital signatures and are generally superior to RSA in terms of security and performance.
  • \n
  • EdDSA signature algorithm: TLS 1.3 also introduces the EdDSA (Edwards-curve Digital Signature Algorithm) signature algorithm based on the Edwards curve. It features efficient performance and strong security for mobile devices and resource-constrained environments.
  • \n
  • RSASSA-PSS signature algorithm: In addition to the traditional RSA-PKCS1 signature algorithm, TLS 1.3 also introduces the RSASSA-PSS signature algorithm, which is a more secure signature method based on RSA and has better attack resistance.
  • \n
  • PSK signature algorithm: TLS 1.3 supports the signature algorithm based on the pre-shared key (PSK), which applies to the PSK handshake mode. This approach does not involve a digital certificate but uses a pre-shared key for verification.
  • \n
\n

TLS 1.3 stops using the DSA (Digital Signature Algorithm) signature algorithm. This is also a notable difference from TLS 1.2. DSA has some security and performance limitations and is rarely used in practice, so TLS 1.3 removed support for DSA certificates.

\n

Other Reinforcements

\n

Additionally, TLS 1.3 includes the following improvements to enhance security

\n
    \n
  • TLS 1.3 does not allow data compression. The data compression feature in earlier versions of TLS could lead to security issues such as CRIME attacks. To avoid this risk, TLS 1.3 removed support for data compression entirely.
  • \n
  • Unlike earlier versions of TLS, TLS 1.3 prohibits renegotiation after the connection has been established. This helps reduce security risk and complexity. Renegotiation may introduce new security holes, and frequent negotiations during the connection process may also cause performance problems.
  • \n
  • All handshake messages following the ServerHello message during the TLS 1.3 handshake are now encrypted. The newly introduced EncryptedExtensions message enables encryption protection of various extensions previously sent in plain text.
  • \n
  • TLS 1.3 adds asymmetric cryptographic protection of the Certificate messages sent from the server to the client. This encryption prevents threats such as man-in-the-middle attacks, information leakage, and certificate forgery, further fortifying the security and privacy of the connection.
  • \n
\n

Performance Boosting

\n

Simplified Handshake

\n

The general trend towards high-speed mobile Internet requires the use of HTTPS/TLS to protect the privacy of all traffic as much as possible. The downside of this is that new connections can become a bit slower. For the client and web server to agree on a shared key, both parties need to exchange security attributes and related parameters through the TLS \"handshake process\". In TLS 1.2 and all protocols before it, the initial handshake process required at least two round-trip message transfers. Compared to pure HTTP, the extra latency introduced by the TLS handshake process of HTTPS can be very detrimental to performance-conscious applications.

\n

TLS 1.3 greatly simplifies the handshake process, requiring only one round trip in most cases, resulting in faster connection establishment and lower latency. Every TLS 1.3 connection will use (EC)DHE-based key exchange, and the parameters supported by the server may be easy to guess (such as ECDHE + x25519 or P-256). Since the options are limited, the client can directly send the (EC)DHE key share information in the first message without waiting for the server to confirm which key exchange it is willing to support. This way, the server can derive the shared secret one round in advance and send encrypted data.

\n

The following diagram compares the message sequences of the handshake process of TLS 1.2 and TLS 1.3. Both operate with public key-based authentication. The TLS 1.3 handshake shown below uses the symbols borrowed from the RFC 8446 specification: '+' indicates a noteworthy extension; '*' indicates an optional message or extension; '[]', '()', and '{}' represent encrypted messages, where the keys used for encryption are different.

\n
\n\"TLS
TLS 1.2 handshake (left) vs. TLS 1.3 handshake (right)
\n
\n

This figure illustrates the following points:

\n
    \n
  • TLS 1.3 removes several messages used by TLS 1.2: ServerHelloDone, ChangeCipherSpec, ServerKeyExchange, and ClientKeyExchange. The contents of TLS 1.2's ServerKeyExchange and ClientKeyExchange messages vary depending on the authentication and key-sharing method being negotiated. In TLS 1.3, this information was moved to the extensions of ClientHello and ServerHello messages. TLS 1.3 completely deprecates ServerHelloDone and ChangeCipherSpec messages, there is no replacement.
  • \n
  • For TLS 1.3 the public key-based authentication mode is probably the most important. It always uses (EC)DHE to achieve forward secrecy. The figure shows that the ClientHello message carries four extensions that are must-haves in this mode: key_share, signature_algorithms, supported_groups, and support_versions.
  • \n
  • During the TLS 1.2 handshake, the exchange of control data requires multiple round trips between the client and server. TLS 1.2's ClientKeyExchange and ChangeCipherSpec messages are carried in separate packets, and the Finished message is the first (and only) encrypted handshake message. The whole process needs to transmit 5-7 data packets.
  • \n
  • During the TLS 1.3 handshake, encrypted Application Data is already sent by the client after the first round trip. As mentioned earlier, the EncryptedExtension message provides privacy protection for ServerHello extensions in earlier versions of TLS. If mutual authentication is required (which is common in IoT deployments), the server will send a CertificateRequest message.
  • \n
  • The Certificate, CertificateVerify, and Finished messages in TLS 1.3 retain the semantics of earlier TLS versions, but they are all asymmetrically encrypted now. Echoing the description in the last section, by encrypting Certificate and CertificateVerify messages, TLS 1.3 better protects against man-in-the-middle and certificate forgery attacks while enhancing the privacy of connections. This is also an important security feature in the design of TLS 1.3.
  • \n
\n

In rare cases, when the server does not support a certain key-sharing method sent by the client, the server can send a new HelloRetryRequest message letting the client know which groups it supports. As the group list has shrunk significantly, this is not expected to happen very often.

\n

0-RTT Session Resumption

\n

0-RTT (Zero Round Trip Time) in TLS 1.3 is a special handshake mode. It allows clients to send encrypted data during the handshake phase, reducing the number of round trips required for connection establishment and enabling faster session resumption. The following is a brief explanation of the 0-RTT working mode:

\n
    \n
  1. Store session tickets: During the normal TLS 1.3 handshake, the client and server generate a data structure called a \"session ticket\" during the handshake. Session tickets contain information about the connection, including key parameters and cipher suites. The server stores the session ticket provided by the client.
  2. \n
  3. 0-RTT handshake: When the client reconnects to the server, it includes the previously saved session ticket in the early_data extension of the ClientHello message, along with encrypted Application Data. The client encrypts 0-RTT data using a pre-shared key (PSK) obtained from a previous connection.
  4. \n
  5. Server Response: After the server receives this message, if it supports 0-RTT mode and can recognize and verify the session ticket, it sends an EncryptedExtensions message, and then confirms the connection in the Finished message. This way, the server can quickly establish a secure connection with 0 round trips. It can also immediately send data to the client to achieve 0-RTT data transmission.
  6. \n
\n

The message sequence of the 0-RTT session resumption and data transmission process of TLS 1.3 is as follows:

\n
\n\"TLS
TLS 1.3 0-RTT
\n
\n

FAQ

\n
    \n
  • Does the TLS 1.3 protocol allow the use of RSA digital certificates?

    \n

    A common misconception is that \"TLS 1.3 is not compatible with RSA digital certificates\". The description in the \"Signature Verification\" section above shows that this is wrong. TLS 1.3 still supports the use of RSA for key exchange and authentication. However, considering the limitations of RSA, it is recommended that when building and deploying new TLS 1.3 applications, ECDHE key exchange algorithms and ECC digital certificates are preferred to achieve higher security and performance.

  • \n
  • During the TLS 1.3 handshake, how does the server request the client to provide a certificate?

    \n

    In some scenarios, the server also needs to verify the identity of the client to ensure that only legitimate clients can access server resources. This is the case with mTLS (mutual TLS). During the TLS 1.3 handshake, the server can specify that the client is required to provide a certificate by sending a special CertificateRequest extension. When the server decides to ask the client for a certificate, it sends a CertificateRequest extension message after the ServerHello message. This extended message contains some necessary parameters, such as a list of supported certificate types, a list of acceptable certificate authorities, and so on. When the client receives it, it knows that the server asked it for a certificate, and it can optionally respond to the request. If the client is also configured to support mTLS and decides to provide a certificate, it provides its certificate chain by sending a Certificate message.

  • \n
  • Is 0-RTT vulnerable to replay attacks?

    \n

    TLS 1.3's 0-RTT session resumption mode is non-interactive and does risk replay attacks in some cases. An attacker may repeat previously sent data to simulate a legitimate request. To avoid and reduce the risk of replay attacks to the greatest extent, TLS 1.3 provides some protection measures and suggestions:

    \n
      \n
    1. The simplest anti-replay method is that the server only allows each session ticket to be used once. For example, the server may maintain a database of all valid tickets that have not been used, deleting each ticket from the database as it is used. If an unknown ticket is received, the server falls back to a full handshake.
    2. \n
    3. The server may limit the time window in which session tickets are accepted, that is, the time range in which 0-RTT data is allowed to be valid. This reduces the chance of an attacker successfully replaying.
    4. \n
    5. Clients and servers should also use 0-RTT data only for stateless requests, that is, requests that do not affect the state of the server such as HTTP GET. For requests that need to modify the state of the server or have an impact, restrict the use of normal handshake patterns only.
    6. \n
    7. Another way to prevent replay is to store the unique value (usually a random value or a PSK bundled value) derived from the ClientHello message, and reject duplicates. Logging all ClientHellos would cause the state to grow without bound, but combined with #2 above, the server can log ClientHellos within a given time window and use obfuscated_ticket_age to ensure that tickets are not duplicated outside the window use.
    8. \n
  • \n
  • If the client does not know whether the server supports TLS 1.3, how could it negotiate the TLS version via handshake?

    \n

    The TLS protocol provides a built-in mechanism for negotiating the running version between endpoints. TLS 1.3 continues this tradition. RFC 8446 Appendix D.1 \"Negotiating with an Older Server\" gives specific instructions:

    \n
    \n

    A TLS 1.3 client who wishes to negotiate with servers that do not support TLS 1.3 will send a normal TLS 1.3 ClientHello containing 0x0303 (TLS 1.2) in ClientHello.legacy_version but with the correct version(s) in the \"supported_versions\" extension. If the server does not support TLS 1.3, it will respond with a ServerHello containing an older version number. If the client agrees to use this version, the negotiation will proceed as appropriate for the negotiated protocol.

    \n
    \n

    The following screenshot of a TLS 1.3 ClientHello message decode demonstrates this. The version number of the handshake message displayed on the left is \"Version: TLS 1.2 (0x0303)\". At the same time, it can be seen that the cipher suite section first lists 3 TLS 1.3 AEAD cipher suites, followed by 14 TLS 1.2 regular cipher suites. On the right, there are 4 extensions - key_share, signature_algorithms, supported_groups, and support_versions. The support_versions extension includes both TLS 1.3 and TLS 1.2 version numbers. This is the TLS version list for the server to choose from. Additionally, the key_share extension includes the client's preferred key-sharing method as x25519 and secp256r1(i.e. NIST P-256)

    \n

  • \n
  • Does the TLS 1.3 protocol work with UDP and EAP?

    \n

    TLS was originally designed for TCP connections, and a variant DTLS (Datagram Transport Layer Security) for UDP was introduced later. Based on TLS 1.3, IETF has released the corresponding upgraded version of the DTLS 1.3 protocol RFC 9147. The design goal of DTLS 1.3 is to provide \"equivalent security guarantees with the exception of order protection / non-replayability\". This protocol was released in April 2022, and currently, there are not many software libraries supporting it.

    \n

    TLS can also be used as an authentication and encryption protocol in various EAP types, such as EAP-TLS, EAP-FAST, and PEAP. Corresponding to TLS 1.3, IETF also published two technical standard documents:

    \n
      \n
    • RFC 9190: EAP-TLS 1.3: Using the Extensible Authentication Protocol with TLS 1.3 (Feb. 2022)
    • \n
    • RFC 9427: TLS-Based Extensible Authentication Protocol (EAP) Types for Use with TLS 1.3 (Jun. 2023)
    • \n
    \n

    Both protocols are also quite new, and the software library updates supporting them are still some time away.

  • \n
\n

NIST Mandate

\n

TLS 1.3 brings new security features and a faster TLS handshake. Since its release in 2018, many Internet services have migrated to this latest version. Nevertheless, widespread adoption across websites takes time. The non-commercial SSL Labs Projects has a dashboard called SSL Pulse that reports TLS/SSL security scan statistics for the most popular Internet sites. Below is the most recent chart of protocol support statistics by July 2023.

\n
\n\"Source:
Source: SSL Pulse - 07/03/2023
\n
\n

As can be seen, of all 135,000+ probed sites the percentage of TLS 1.3 support is about 63.5%. That means there are still close to 50 thousand sites that do not leverage the security and performance benefits of TLS 1.3. Why? The decision to migrate a website to a new protocol version like TLS 1.3 can be complex and influenced by various factors. The top 3 common reasons hindering TLS 1.3 migration are

\n
    \n
  • Compatibility Concerns: Some websites might have users who are still using outdated browsers or operating systems that do not support TLS 1.3. These websites need to maintain backward compatibility to ensure that all users can access their content securely.
  • \n
  • Resource Constraints: Migration involves technical updates, configuration changes, and testing. Smaller websites or those with limited resources might face challenges in allocating the necessary time and effort to make these changes.
  • \n
  • Third-Party Dependencies: Many websites rely on third-party services, content delivery networks, or other components. If these services do not yet support TLS 1.3, the website might delay migration to avoid disruptions or compatibility issues with these dependencies.
  • \n
\n

However, for network hardware/software vendors who want their products on the procurement list of any US public sector organization, there is a coming NIST mandate to make TLS 1.3 available by January 2024. This is stipulated in the National Institute of Standards and Technology Special Publication (NIST SP) 800-52 Rev. 2: Guidelines for the Selection, Configuration, and Use of Transport Layer Security (TLS) Implementations. Quoted from NIST SP 800-52 Rev. 2

\n
\n

3.1 Protocol Version Support

\n

Servers that support government-only applications shall be configured to use TLS 1.2 and should be configured to use TLS 1.3 as well. ...

\n

Servers that support citizen or business-facing applications (i.e., the client may not be part of a government IT system)10 shall be configured to negotiate TLS 1.2 and should be configured to negotiate TLS 1.3. ...

\n

Agencies shall support TLS 1.3 by January 1, 2024. After this date, servers shall support TLS 1.3 for both government-only and citizen or business-facing applications. In general, servers that support TLS 1.3 should be configured to use TLS 1.2 as well. However, TLS 1.2 may be disabled on servers that support TLS 1.3 if it has been determined that TLS 1.2 is not needed for interoperability.

\n
\n

As in the RFC documents, \"shall\" above is a strong keyword that means that the definition is an absolute requirement of the specification. So this NIST publication requires all servers owned by the US government agencies to be able to support TLS 1.3 by 01/01/2024. They must run a minimum TLS version 1.2 by default and can be configured to do TLS 1.3 only if desired.

\n

It is worth pointing out that this is not an official FIPS requirement, so not mandatory for the FIPS 140-3 certification at present. Besides, this NIPS document has a clear scope statement: \"The scope is further limited to TLS when used in conjunction with TCP/IP. For example, Datagram TLS (DTLS), which operates over datagram protocols, is outside the scope of these guidelines. NIST may issue separate guidelines for DTLS at a later date.\" Based on this, we can infer that DTLS and EAP are out of consideration for this mandate.

\n

Enabling TLS 1.3

\n

The enhanced security and optimized performance of TLS 1.3 make it the first choice for securing communication of various network applications. Now we demonstrate how to enable TLS 1.3 function in three commonly used web server software Apache, Nginx, and Lighttpd.

\n

NOTE: The implementation of many secure network communication applications relies on third-party SSL/TLS software libraries, such as wolfSSL, GnuTLS, NSS, and OpenSSL. Therefore, to enable the TLS 1.3 function of these applications, you need to ensure that the libraries they link with support TLS 1.3. For example, in September 2018, the popular OpenSSL project released version 1.1.1 of the library, with support for TLS 1.3 as its \"top new feature\".

\n
\n

Apache HTTP Server

\n

The Apache HTTP Server is an open-source web server software from the Apache Software Foundation. Apache HTTP server is widely used and is one of the most popular web server software due to its cross-platform and security. Apache supports a variety of features, many of which extend core functionality through compiled modules, such as authentication schemes, proxy servers, URL rewriting, SSL/TLS support, and compiling interpreters such as Perl/Python into the server.

\n

Apache HTTP Server has built-in support for TLS 1.3 since version 2.4.36, no need to install any additional modules or patches. The following command can be used to verify the version of the server

\n
$ apache2ctl -v 
Server version: Apache/2.4.41 (Ubuntu)
Server built: 2020-04-13T17:19:17
\n

Once the version is verified, the SSLProtocol line of the configuration file can be updated. The following will enable the Apache HTTP server to only support the TLS 1.3 protocol

\n
/etc/apache2/mods-available/ssl.conf
# Only enable TLS 1.3
SSLProtocol -all +TLSv1.3
\n

If the server needs to be compatible with clients that support TLS 1.2, you can add +TLSv1.2. After updating the configuration, restart the service

\n
$ sudo service apache2 restart
\n

Nginx Web Server

\n

Nginx is a high-performance web server based on an asynchronous framework and modular design. It can also be used for reverse proxy, load balancer, and HTTP caching applications. It is free and open-source software released under the terms of a BSD-like license. Nginx uses an asynchronous event-driven approach to request processing, which can provide more predictable performance under high load. The current market share of Nginx is almost equal to that of the Apache HTTP server.

\n

Nginx supports TLS 1.3 from version 1.13.0. The following command can be used to verify its version

\n
$ nginx -v
nginx version: nginx/1.17.10 (Ubuntu)
\n

In the Nginx configuration file, find the server block and modify the ssl_protocols line to enable TLS 1.3:

\n
/etc/nginx/nginx.conf
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
root /var/www/example.com/public;

ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private-key.key;

# support TLS 1.2 and TLS 1.3
ssl_protocols TLSv1.2 TLSv1.3;

...
}
\n

If you don't need to continue to support TLS 1.2, delete the TLSv1.2 there. After the modification is complete, you can run the following command to test the configuration of Nginx, and then restart the service

\n
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

$ sudo service nginx restart
\n

Lighttpd Web Server

\n

Lighttpd is a lightweight open-source web server software. It focuses on high performance, low memory footprint, and fast responsiveness. Lighttpd is suitable for serving web applications and static content of all sizes. Its design goal is to provide an efficient, flexible, and scalable web server, especially suitable for high-load and resource-constrained (such as embedded systems) environments.

\n

The first Lighttpd release to support TLS 1.3 is version 1.4.56. Starting with this version, the minimum version of TLS that Lighttpd supports by default is TLS 1.2. That is to say, Lighttpd supports TLS 1.2 and TLS 1.3 if no corresponding configuration file modification is made.

\n

To limit the use of Lighttpd to only the TLS 1.3 feature, first make sure the mod_openssl module is loaded. Then in the configuration file lighttpd.conf, find the server.modules section, and add the following ssl.openssl.ssl-conf-cmd line:

\n
/etc/lighttpd/lighttpd.conf
server.modules += ("mod_openssl")
$SERVER["socket"] == ":443" {
ssl.engine = "enable"
ssl.pemfile = "/path/to/your/cert.pem"
ssl.privkey = "/path/to/your/privkey.pem"
ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.3",
"Options" => "-ServerPreference")
}
\n

This will set the minimum version supported by Lighttpd to be TLS 1.3. Finally, save and reload the Lighttpd configuration for the changes to take effect:

\n
sudo lighttpd -t -f /etc/lighttpd/lighttpd.conf # check configuration
sudo systemctl reload lighttpd
\n","categories":["Technical Know-how"],"tags":["Cryptography","Network Security"]},{"title":"Solve picoCTF's RSA Challenge Sum-O-Primes","url":"/en/2022/08/20/picoCTF-Sum-O-Primes/","content":"

By chance, I came across a picoCTF RSA challenge called Sum-O-Primes. This problem is not difficult, you can do it by knowing the basics of the RSA algorithm. In addition, if you are familiar with the history of the evolution of the RSA algorithm, you can find a second ingenious fast solution.

\n

picoCTF Project

\n

picoCTF is a free computer security education program created by security and privacy experts at Carnegie Mellon University. It uses original content built on the CTF (Capture the Flag) framework to provide a variety of challenges. It provides participants with valuable opportunities to systematically learn cybersecurity knowledge and gain practical experience.

\n

The collection of practice questions for picoCTF is called picoGym. The general problem solution is to search or decipher a string in the format \"picoCTF{...}\" from the given information, that is, the flag to be captured. As shown in the figure below, picoGym currently contains 271 cybersecurity challenge exercises, covering general skills, cryptography, reverse engineering, forensics, and other fields.

\n

\n

Sum-O-Primes Challenge

\n

There are 50 cryptography-related challenges in picoGym, one of which is Sum-O-Primes. The task of this challenge is simple and explained as follows:

\n
\n

We have so much faith in RSA we give you not just the product of the primes, but their sum as well!

\n\n
\n

That is, we not only give the product of the two prime numbers used by RSA but also tell you their sum. How are these given? You need to discover by yourself from the rest of the information. After clicking the two links and downloading the file, open the first Python file:

\n
gen.py
#!/usr/bin/python

from binascii import hexlify
from gmpy2 import mpz_urandomb, next_prime, random_state
import math
import os
import sys

if sys.version_info < (3, 9):
import gmpy2
math.gcd = gmpy2.gcd
math.lcm = gmpy2.lcm

FLAG = open('flag.txt').read().strip()
FLAG = int(hexlify(FLAG.encode()), 16)
SEED = int(hexlify(os.urandom(32)).decode(), 16)
STATE = random_state(SEED)

def get_prime(bits):
return next_prime(mpz_urandomb(STATE, bits) | (1 << (bits - 1)))

p = get_prime(1024)
q = get_prime(1024)

x = p + q
n = p * q

e = 65537

m = math.lcm(p - 1, q - 1)
d = pow(e, -1, m)

c = pow(FLAG, e, n)

print(f'x = {x:x}')
print(f'n = {n:x}')
print(f'c = {c:x}')
\n

If you have basic Python programming skills and understand the principles of the RSA algorithm, you should be able to read the above program quickly. What it does is:

\n
    \n
  1. Open the file flag.txt to read the content. Then use the hexlify and int functions to convert it to an integer and store the result in a variable FLAG.
  2. \n
  3. Call the function get_prime to generate two prime numbers, store their sum in x and their product in n. Then assign 65537 to e and calculate the RSA private exponent d.
  4. \n
  5. Use standard pow functions to perform modular exponentiation, which implements RSA encryption to encrypt plaintext FLAG into ciphertext c.
  6. \n
  7. Print out x, n, and c.
  8. \n
\n

Open the second file, which is apparently the output of the first program in Python:

\n
output.txt
x = 154ee809a4dc337290e6a4996e0717dd938160d6abfb651736d9f5d524812a659b310ad1f221196ee8ab187fa746a1b488a4079cddfc5db08e78be0d96c83c01e9bb42420b40d6f0ad9f220633459a6dc058bb01c517386bfbd2d4811c9b08558b0e05534768581a74884758d15e15b4ef0dbd6a338bf1f52eed4f137957737d2
n = 6ce91e471f1df651b0d275d6d5522703feecdd77e7821a2caf9514104c059781c1b2e64772d9220addd657ecbd4e6cb8b5941608f6ab54bd5760074a5cd5854920439422192d2ee8912f1ebcc0d97714f209ee2a22e2da60e071541cb7e0772373cfea71831673378ee6432e63abfd14db0d4aa601928923253f9edd419ce96f4d68ce0aa3e6d6b530cd46eefbdac93038ce949c9dd2e573a47471cf8223f88b96e00a92f4d47fd277c42c4075b5e99b41a9f279f442bc0d533b9ddc50592e369e7026b3f7afaa8edf8972f0c3055f4de67a0eea963f099a32e1539de1d1727abadd9235f66371998ec883d1f89b8d907270842818cae49cd5c7f906c4752e81
c = 48b89662b9718fb391c96527272bf74c27810edaca09b63e694af9d11608010b1db9aedd1c867849371121941a1ccac610f7b28b92fa2f981babe816e6d3ecfab83514ed7e18e2b23fc3b96c7002ff47da897e9f2a9cb1b4e245396589e0b72affb73568a2016031555d2a46557919e44a15cd43fe9e1881d40dce1d1e36625e63b1472d3c317898102943072e06d79688c96b6ee2e584002c66497a9cdc48c38aa0548a7bc4fed9b4c23fcd493f38ece68788ef37a559b7f20c6941fcf8e567d9f50807259a7f11fa7a01d3125a1f7609cd94781f224ec8351605354b11c6b078fe015826342c3271ee3af4b99bb0a538b1e6b845594ee6546be8abd22ef2bd
\n

Once you understand the meaning of the question, you can make a judgment immediately —— if you can decrypt the ciphertext c and retrieve the plaintext FLAG, you can get the original content of flag.txt, that is, capture the flag.

\n

Conventional Solution

\n

RSA decryption requires a private key exponent d. Referring to the steps of the RSA algorithm below, it is obvious that this demands integer factorization for large prime numbers p and q first.

\n
    \n
  1. Choose two large prime numbers \\(p\\) and \\(q\\), compute \\(n=pq\\)
  2. \n
  3. Compute Carmichael function \\(\\lambda(n)=\\operatorname{lcm}(p − 1, q − 1)\\) the product, \\(\\operatorname{lcm}\\) is a function to find the least common multiple
  4. \n
  5. Choose any number \\(e\\) that is less than and coprime to \\(\\lambda(n)\\), then compute \\(d\\), the modular multiplicative inverse of \\(e\\) regarding \\(\\lambda(n)\\), \\(d\\equiv e^{-1}\\pmod {\\lambda(n)}\\)
  6. \n
  7. \\((n,e)\\) is the RSA public key, \\((n,d)\\) the RSA private key
  8. \n
  9. Use the public key to encrypt the plaintext \\(m\\), the formula is \\(c\\equiv m^e\\pmod n\\)
  10. \n
  11. Use the private key to decrypt the ciphertext \\(c\\), the formula is \\(m\\equiv c^d\\pmod n\\)
  12. \n
\n

From here, the challenge becomes a problem that, knowing the sum and product of two large prime numbers known, find these two large prime numbers. That is, to solve a system of quadratic linear equations

\n

\\[\n\\left\\{\n\\begin{aligned}\np+q &=n \\\\ \np*q &=x\n\\end{aligned} \n\\right. \n\\]

\n

Using the knowledge of elementary mathematics, the above equations can be transformed into a quadratic equation \\[p^2 - x * p + n = 0\\]

\n

Obviously, \\(p\\) and \\(q\\) are its two roots. According to the quadratic formula

\n

\\[(p,q)={\\frac {x}{2}}\\pm {\\sqrt {\\left({\\frac {x}{2}}\\right)^{2}-n}}\\]

\n

We can get \\(p\\) and \\(q\\). The rest of the work is easy. The code to compute \\(d\\) from \\(p\\) and \\(q\\) can be copied directly from lines 28, 30, and 31 in gen.py. The final complete Python problem-solving code is as follows:

\n
import math

file = open('output.txt', 'r')
Lines = file.readlines()
file.close()

x = int((Lines[0].split())[2], 16) # x = p + q
n = int((Lines[1].split())[2], 16) # n = p * q
c = int((Lines[2].split())[2], 16) # Ciphertext

def solve_rsa_primes(s: int, m: int) -> tuple:
'''
Solve RSA prime numbers (p, q) from the quadratic equation
p^2 - s * p + m = 0 with the formula p = s/2 +/- sqrt((s/2)^2 - m)

Input: s - sum of primes, m - product of primes
Output: (p, q)
'''
half_s = s >> 1
tmp = math.isqrt(half_s ** 2 - m)
return int(half_s + tmp), int(half_s - tmp);

# Now run with the real input
p, q = solve_rsa_primes(x, n)
m = math.lcm(p - 1, q - 1)
e = 65537
d = pow(e, -1, m)
FLAG = pow(c, d, n)
print(FLAG.to_bytes((FLAG.bit_length() + 7) // 8, 'big'))
\n

The above program defines a general function solve_rsa_primes to solve two large prime numbers. After it gets d, the same pow function is called to decrypt, and finally the plaintext is converted from a large integer to a byte sequence and printed out. The result of running this program is

\n
b'picoCTF{pl33z_n0_g1v3_c0ngru3nc3_0f_5qu4r35_92fe3557}'
\n

BINGO! Capture the Flag successfully!

\n

Note: The function solve_rsa_primes calls math.isqrt to compute the integer square root of the given integer. This is indispensable! If it is written incorrectly with math.sqrt, the following overflow error will occur

\n
>>>
=============== RESTART: /Users/zixi/Downloads/Sum-O-Primes.py ==============
Traceback (most recent call last):
File "/Users/zixi/Downloads/Sum-O-Primes.py", line 35, in <module>
p, q = solve_rsa_primes(x, n)
File "/Users/zixi/Downloads/Sum-O-Primes.py", line 31, in solve_rsa_primes
tmp = math.sqrt(int(half_s ** 2 - m))
OverflowError: int too large to convert to float
\n

This error happens because math.sqrt uses floating-point arithmetic but fails to convert large integers to floating-point numbers.

\n
\n

Quick Solution

\n

The conventional solution to this problem has to solve a quadratic equation, so the integer square root operation is essential. Is there a solution that doesn't need a square root operation? The answer is yes.

\n

In the original RSA paper, the public exponent \\(e\\) and the private exponent \\(d\\) have the relationship as the following equation

\n

\\[d⋅e≡1\\pmod{\\varphi(n)}\\]

\n

Here the modular is the Euler's totient function \\(\\varphi(n)=(p-1)(q-1)\\). Since \\(\\varphi(N)\\) is always divisible by \\(\\lambda(n)\\), any d satisfying the above also satisfies \\(d⋅e≡1\\pmod{\\lambda(n)}\\), thus the private exponent is not unique. Although the calculated \\(d>\\lambda(n)\\), the square root operation can be avoided when applied to the Sum-O-Primes problem. This is because \\[\n\\begin{aligned}\n\\varphi(n)&=(p-1)(q-1)\\\\\n&=pq-(p+q)+1\\\\\n&=n-x+1\n\\end{aligned}\n\\]

\n

Hereby the formula for computing the private exponent becomes

\n

\\[\n\\begin{aligned}\nd&≡e^{-1}\\pmod{\\varphi(n)}\\\\\n&≡e^{-1}\\pmod{(n-x+1)}\n\\end{aligned}\n\\]

\n

Now that \\(n\\) and \\(x\\) are readily available, this method does not require finding \\(p\\) and \\(q\\) first, and naturally, there is no need for a square root operation. The Python code for this new solution is very concise

\n
d1 = pow(e, -1, n - x + 1)
FLAG = pow(c, d1, n)
print(FLAG.to_bytes((FLAG.bit_length() + 7) // 8, 'big'))

print("d = ", d)
print("d1 = ", d1)
assert(d1>d)
print("d1/d = ", d1/d)
\n

To compare these two solutions, 4 lines of print and assert statements are added at the end. The execution result of this code is

\n
>>>
=============== RESTART: /Users/zixi/Downloads/Sum-O-Primes.py ==============
b'picoCTF{pl33z_n0_g1v3_c0ngru3nc3_0f_5qu4r35_92fe3557}'
d = 1590433953643304448870807755026766943237397482033766155980367645454600169745357277163199312196609495875891431590581528929277583062406061101224041553945564552302546648687338536694903918084325519368961617691238793972703013656395301935576994660878296156727353260699130612675943209520489312860964899655070852366584778594425834982623831654304915478835573020874834723387183369976749895237126850604587166433366381884290402338703266523462767765540527102747754912478720160791675179128443712374832507705614160658601242723842366612805686436771142338154848447759947887908800687914418476358484536216953925324788380823429735298973
d1 = 11901952834426939436403812982514571575614906347331071933175950931208083895179963694981295931167346168378938101218143770786299673201984563299831132533757316974157649670783507276616478666261648674806749337918514985951832847720617452268824430679672778783943236259522437088812130196067329355430038927225825521934485847159262037514154059696664148362902872186817856316128403800463106817000251243818717005827615275821709043532925457271839955998044684537152992871171338447136672661193487297988293156428071068861346467230927990425182893890027896377626007826573834588309038513191969376781172191621785853174152547091371818954913
d1/d = 7.483462489694971
\n

As shown above, this solution also succeeds in capturing the flag. The \\(d\\) value (d1) calculated by the new solution is more than 7 times that of the conventional solution.

\n

Click here to download all the code of this article: Sum-O-Primes.py.gz

\n","categories":["Technical Know-how"],"tags":["Cryptography","Python Programming","CTF"]},{"title":"Notes on Using uClibc Standard Library in Embedded Linux System","url":"/en/2023/03/10/uClibc-tips/","content":"

uClibc is a small and exquisite C standard library for embedded Linux systems. It is widely used in the development of low-end embedded systems and Internet of Things devices. Here are some recent experiences to provide convenience for engineers who need to solve similar problems or meet corresponding requirements.

\n

Low-level programming is good for the programmer's soul.
John Carmack (American computer programmer and video game developer, co-founder of the video game company id Software)

\n
\n

Introduction to uClibc

\n

uClibc (sometimes written as μClibc) is a small C standard library designed to provide support for embedded systems and mobile devices using operating systems based on the Linux kernel. uClibc was originally developed to support μClinux, a version of Linux not requiring a memory management unit thus especially suited for microcontroller systems. The \"uC\" in its name is the abbreviation of microcontroller in English, where \"u\" is a Latin script typographical approximation of the Greek letter μ that stands for \"micro\".

\n

uClibc is a free and open-source software licensed under the GNU Lesser GPL, and its library functions encapsulate the system calls of the Linux kernel. It can run on standard or MMU-less Linux systems and supports many processors such as i386, x86-64, ARM, MIPS, and PowerPC. Development of uClibc started in 1999 and was written mostly from scratch, but also absorbed code from glibc and other projects. uClibc is much smaller than glibc. While glibc aims to fully support all relevant C standards on a wide range of hardware and kernel platforms, uClibc focuses on embedded Linux systems. It also allows developers to enable or disable some features according to the memory space design requirements.

\n

The following records show the list of C standard library files in two similar embedded systems. The first uses glibc-2.23 version, and the second integrates uClibc-0.9.33.2 version. The total size of glibc library files is more than 2MB, while the uClibc library files add up to less than 1MB. It can be seen that using uClibc does save a lot of storage space.

\n
STM1:/# find . -name "*lib*2.23*" | xargs ls -alh
-rwxr-xr-x 1 root root 9.6K Jan 1 1970 ./lib/libanl-2.23.so
-rwxr-xr-x 1 root root 1.1M Jan 1 1970 ./lib/libc-2.23.so
-rwxr-xr-x 1 root root 177.5K Jan 1 1970 ./lib/libcidn-2.23.so
-rwxr-xr-x 1 root root 29.5K Jan 1 1970 ./lib/libcrypt-2.23.so
-rwxr-xr-x 1 root root 9.5K Jan 1 1970 ./lib/libdl-2.23.so
-rwxr-xr-x 1 root root 429.4K Jan 1 1970 ./lib/libm-2.23.so
-rwxr-xr-x 1 root root 65.8K Jan 1 1970 ./lib/libnsl-2.23.so
-rwxr-xr-x 1 root root 17.5K Jan 1 1970 ./lib/libnss_dns-2.23.so
-rwxr-xr-x 1 root root 33.6K Jan 1 1970 ./lib/libnss_files-2.23.so
-rwxr-xr-x 1 root root 90.5K Jan 1 1970 ./lib/libpthread-2.23.so
-rwxr-xr-x 1 root root 65.7K Jan 1 1970 ./lib/libresolv-2.23.so
-rwxr-xr-x 1 root root 25.9K Jan 1 1970 ./lib/librt-2.23.so
-rwxr-xr-x 1 root root 9.5K Jan 1 1970 ./lib/libutil-2.23.so

STM2:/# find . -name "*lib*0.9.33*" | xargs ls -alh
-rwxr-xr-x 1 root root 28.0K Jan 1 1970 ./lib/ld-uClibc-0.9.33.2.so
-rwxr-xr-x 1 root root 36.1K Jan 1 1970 ./lib/libcrypt-0.9.33.2.so
-rwxr-xr-x 1 root root 16.2K Jan 1 1970 ./lib/libdl-0.9.33.2.so
-rwxr-xr-x 1 root root 72.1K Jan 1 1970 ./lib/libm-0.9.33.2.so
-rwxr-xr-x 1 root root 116.4K Jan 1 1970 ./lib/libpthread-0.9.33.2.so
-rwxr-xr-x 1 root root 16.2K Jan 1 1970 ./lib/librt-0.9.33.2.so
-rwxr-xr-x 1 root root 28.3K Jan 1 1970 ./lib/libthread_db-0.9.33.2.so
-rwxr-xr-x 1 root root 621.4K Jan 1 1970 ./lib/libuClibc-0.9.33.2.so
-rwxr-xr-x 1 root root 8.1K Jan 1 1970 ./lib/libubacktrace-0.9.33.2.so
-rwxr-xr-x 1 root root 4.1K Jan 1 1970 ./lib/libutil-0.9.33.2.so
\n

IPv6 and Interface API

\n

With the steady growth of IPv6 deployment, adding IPv6 protocol stack support for embedded systems has become necessary. In a software project that adds IPv4/IPv6 dual-stack function to devices using uClibc, it is found that there is an application link error - undefined reference to getifaddrs. getifaddrs() is a very useful function, we can call it to get the address information of all the network interfaces of the system. Query the Linux programming manual:

\n
SYNOPSIS
#include <sys/types.h>
#include <ifaddrs.h>

int getifaddrs(struct ifaddrs **ifap);
...
\t
DESCRIPTION
The getifaddrs() function creates a linked list of structures
describing the network interfaces of the local system, and stores
the address of the first item of the list in *ifap.
...

VERSIONS
The getifaddrs() function first appeared in glibc 2.3, but before
glibc 2.3.3, the implementation supported only IPv4 addresses;
IPv6 support was added in glibc 2.3.3. Support of address
families other than IPv4 is available only on kernels that
support netlink.
...
\n

The last sentence above is key: only kernels supporting netlink can support address families other than IPv4. The Linux kernel version running on this system is 3.x, which supports netlink. So, could there be a problem with uClibc's support for netlink that causes getifaddrs() not to get compiled?

\n

With this question in mind, search the source code directory of uClibc and find the C file that implements the function getifaddrs():

\n
libc/inet/ifaddrs.c
...
#if __ASSUME_NETLINK_SUPPORT
#ifdef __UCLIBC_SUPPORT_AI_ADDRCONFIG__
/* struct to hold the data for one ifaddrs entry, so we can allocate
everything at once. */
struct ifaddrs_storage
{
struct ifaddrs ifa;
union
{
/* Save space for the biggest of the four used sockaddr types and
avoid a lot of casts. */
struct sockaddr sa;
struct sockaddr_ll sl;
struct sockaddr_in s4;
#ifdef __UCLIBC_HAS_IPV6__
struct sockaddr_in6 s6;
#endif
} addr, netmask, broadaddr;
char name[IF_NAMESIZE + 1];
};
#endif /* __UCLIBC_SUPPORT_AI_ADDRCONFIG__ */
...
#ifdef __UCLIBC_SUPPORT_AI_ADDRCONFIG__
...
int
getifaddrs (struct ifaddrs **ifap)
...
#endif /* __UCLIBC_SUPPORT_AI_ADDRCONFIG__ */
...
#endif /* __ASSUME_NETLINK_SUPPORT */
\n

Just as expected! The implementation of the entire function and the definition of the associated data structure ifaddrs_storageare are placed under three nested conditional compilation directives with macros defined as

\n
    \n
  1. __ASSUME_NETLINK_SUPPORT
  2. \n
  3. __UCLIBC_SUPPORT_AI_ADDRCONFIG__
  4. \n
  5. __UCLIBC_HAS_IPV6__
  6. \n
\n

Therefore, as long as their corresponding configuration lines are opened, the problem should be solved. After changing the configuration file of uClibc as follows, rebuild the dynamic link library of uClibc, then the application can be made successfully:

\n
--- a/toolchain/uClibc/config-0.9.33.2/common
+++ b/toolchain/uClibc/config-0.9.33.2/common
@@ -147,7 +147,8 @@ UCLIBC_HAS_RPC=y
UCLIBC_HAS_FULL_RPC=y
-# UCLIBC_HAS_IPV6 is not set
+UCLIBC_HAS_IPV6=y
-# UCLIBC_USE_NETLINK is not set
+UCLIBC_USE_NETLINK=y
+UCLIBC_SUPPORT_AI_ADDRCONFIG=y
UCLIBC_HAS_BSD_RES_CLOSE=y
\n

SHA-2 Hash Function

\n

Embedded systems often need to provide remote SSH login services for system administrators, which requires the creation of system users and their passwords. Linux saves the user name and the hashed password in the /etc/shadow file. The storage format of the hash value follows a de facto standard called the Modular Crypt Format (MCF for short), and its format is as follows:

\n
$<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
\n

Here

\n
    \n
  • id: indicates the identifier of the hash algorithm (eg 1 for MD5, 5 for SHA-256, 6 for SHA-512)
  • \n
  • param=value: Hash complexity parameters (such as the number of rounds/iterations) and their values
  • \n
  • salt: radix-64 (charset [+/a-zA-Z0-9]) encoded salt
  • \n
  • hash: the radix-64 encoded hash result of the password and salt
  • \n
\n

With the rapid increase of computing power following Moore's Law, the previously commonly used MD5-based hashing scheme has become obsolete because it is too vulnerable to attack. Newly designed systems are now switched to the SHA-512 hashing scheme, corresponding to $6$ seen in the /etc/shadow file.

\n

Both generation and verification of user password hash values ​​can be implemented with the POSIX C library function named crypt. This function is defined as follows:

\n
char *crypt(const char *key, const char *salt)
\n

The input parameter key points to the string containing the user's password, and salt points to a string in the format $<id>$<salt> indicating the hash algorithm and salt to be used. Most Linux distributions use the crypt function provided by the glibc library. The following figure summarizes the augmented crypt function in Glibc:

\n

\n

In an embedded Linux system integrating uClibc, uClibc provides support for the crypt function. But the test found that it returned a null pointer for the correct \\(6\\) input! What's going on here?

\n

The answer lies in the uClibc's implementation of the crypt function. Find the corresponding C source code:

\n
libcrypt/crypt.c
#include <unistd.h>
#include <crypt.h>
#include "libcrypt.h"

char *crypt(const char *key, const char *salt)
{
const unsigned char *ukey = (const unsigned char *)key;
const unsigned char *usalt = (const unsigned char *)salt;

if (salt[0] == '$') {
if (salt[1] && salt[2] == '$') { /* no blowfish '2X' here ATM */
if (*++salt == '1')
return __md5_crypt(ukey, usalt);
#ifdef __UCLIBC_HAS_SHA256_CRYPT_IMPL__
else if (*salt == '5')
return __sha256_crypt(ukey, usalt);
#endif
#ifdef __UCLIBC_HAS_SHA512_CRYPT_IMPL__
else if (*salt == '6')
return __sha512_crypt(ukey, usalt);
#endif
}
/* __set_errno(EINVAL);*/ /* ENOSYS might be misleading */
return NULL;
}
return __des_crypt(ukey, usalt);
}
\n

Aha! It turns out that it only does MD5 hashing by default, and the codes of SHA-256 and SHA-512 need their own conditional compilation macro definitions. This is easy to handle, just edit the configuration file of uClibc and open the latter two.

\n
--- a/toolchain/uClibc/config-0.9.33.2/common
+++ b/toolchain/uClibc/config-0.9.33.2/common
@@ -151,8 +151,8 @@ UCLIBC_HAS_REGEX_OLD=y
UCLIBC_HAS_RESOLVER_SUPPORT=y
-# UCLIBC_HAS_SHA256_CRYPT_IMPL is not set
-# UCLIBC_HAS_SHA512_CRYPT_IMPL is not set
+UCLIBC_HAS_SHA256_CRYPT_IMPL=y
+UCLIBC_HAS_SHA512_CRYPT_IMPL=y
UCLIBC_HAS_SHADOW=y
\n

Finally, take a look at the program that comes with uClibc to test the SHA-512 hash algorithm. It clearly lists the data structures defined by the test code, including the salt, the input password, and the expected output, as well as several test vectors:

\n
test/crypt/sha512c-test.c
static const struct
{
const char *salt;
const char *input;
const char *expected;
} tests[] =
{
{ "$6$saltstring", "Hello world!",
"$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJu"
"esI68u4OTLiBFdcbYEdFCoEOfaS35inz1" },
{ "$6$rounds=10000$saltstringsaltstring", "Hello world!",
"$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sb"
"HbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v." },
...
{ "$6$rounds=10$roundstoolow", "the minimum number is still observed",
"$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1x"
"hLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX." },
};
\n

It can be seen that the last test case defines the round value 10 ($6$rounds=10$roundstoolow), while the output shows that the round is 1000 (rounds=1000). This confirms that the crypt function implementation of uClibc matches the augmented function of Glibc - in order to ensure security, if the input specified round is too small, crypt will automatically set to the minimum round of 1000.

\n

DNS Security Patch

\n

In early May 2022, Nozomi Networks, a company focused on providing security solutions for industrial and critical infrastructure environments, released a newly discovered uClibc security vulnerability CVE-2022-30295. This vulnerability exists in the Domain Name System (DNS) implementation of all versions of uClibc and its fork uClibc-ng (prior to version 1.0.41). Since the implementation uses predictable transaction IDs when making DNS requests, there is a risk of DNS cache poisoning attacks.

\n

Specifically, applications often call gethostbyname library functions to resolve a network address for a given hostname. uClibc/uClibc-ng internally implements a __dns_lookup function for the actual DNS domain name request and response processing. Taking the last version 0.9.33.2 of uClibc as an example, the screenshot below shows the problematic code in the function __dns_lookup:

\n

\n

Referring to line 1308, at the first DNS request, the variable local_id is initialized to the transaction ID value of the last DNS request (stored in a static variable last_id). Line 1319 is the actual culprit, it simply updates the old local_id value by incrementing it by 1. This new value is stored back into the variable last_id, as shown on line 1322. Finally, on line 1334, the value of local_id is copied into the structure variable h, which represents the actual content of the DNS request header. This code works pretty much in all available versions of uClibc and uClibc-ng prior to version 1.0.41.

\n

This implementation makes the transaction ID in the DNS request predictable, because the attacker can estimate the value of the transaction ID in the next request as long as he/she detects the current transaction ID. By exploiting this vulnerability, an attacker can disrupt/poison the host's DNS cache by crafting a DNS response containing the correct source port and winning the competition with the legitimate response returned by the DNS server, making the network data of the application in the host system be directed to a trap site set by the attacker.

\n

The maintainers of uClibc-ng responded quickly to the announcement of this security vulnerability. They submitted a fix in mid-May 2022, and released version 1.0.41 including this patch at the end of that month. For uClibc, since this C standard library has stopped releasing any new versions since 2012, it is currently in an unmaintained state, so system R&D engineers need to come up with their repair. The following uClibc patches are available for reference:

\n
diff --git a/libc/inet/resolv.c b/libc/inet/resolv.c
index 31e63810b..c2a8e2be4 100644
--- a/libc/inet/resolv.c
+++ b/libc/inet/resolv.c
@@ -315,6 +315,7 @@ Domain name in a message can be represented as either:
#include <sys/utsname.h>
#include <sys/un.h>
#include <sys/stat.h>
+#include <fcntl.h>
#include <sys/param.h>
#include <bits/uClibc_mutex.h>
#include "internal/parse_config.h"
@@ -1212,6 +1213,20 @@ static int __decode_answer(const unsigned char *message, /* packet */
return i + RRFIXEDSZ + a->rdlength;
}

+uint16_t dnsrand_next(int urand_fd, int def_value) {
+ if (urand_fd == -1) return def_value;
+ uint16_t val;
+ if(read(urand_fd, &val, 2) != 2) return def_value;
+ return val;
+}
+
+int dnsrand_setup(int *urand_fd, int def_value) {
+ if (*urand_fd > 0) return dnsrand_next(*urand_fd, def_value);
+ *urand_fd = open("/dev/urandom", O_RDONLY);
+ if (*urand_fd == -1) return def_value;
+ return dnsrand_next(*urand_fd, def_value);
+}
+
/* On entry:
* a.buf(len) = auxiliary buffer for IP addresses after first one
* a.add_count = how many additional addresses are there already
@@ -1237,6 +1252,7 @@ int __dns_lookup(const char *name,
/* Protected by __resolv_lock: */
static int last_ns_num = 0;
static uint16_t last_id = 1;
+ static int urand_fd = -1;

int i, j, fd, rc;
int packet_len;
@@ -1305,7 +1321,7 @@ int __dns_lookup(const char *name,
}
/* first time? pick starting server etc */
if (local_ns_num < 0) {
- local_id = last_id;
+ local_id = dnsrand_setup(&urand_fd, last_id);
/*TODO: implement /etc/resolv.conf's "options rotate"
(a.k.a. RES_ROTATE bit in _res.options)
local_ns_num = 0;
@@ -1316,8 +1332,9 @@ int __dns_lookup(const char *name,
retries_left--;
if (local_ns_num >= __nameservers)
local_ns_num = 0;
- local_id++;
+ local_id = dnsrand_next(urand_fd, local_id++);
local_id &= 0xffff;
+ DPRINTF("local_id:0x%hx\\n", local_id);
/* write new values back while still under lock */
last_id = local_id;
last_ns_num = local_ns_num;
\n

This uClibc patch is a simplified version of the uClibc-ng official patch. Its core is to read a double-byte random number from the system /dev/urandom file, and then use it to set the original local_id, the transaction ID of the DNS request. /dev/urandom is a special device file of the Linux system. It can be used as a non-blocking random number generator, which will reuse the data in the entropy pool to generate pseudo-random data.

\n

Note that in the above patch, the function dnsrand_setup must first check urand_fd whether it is positive, and only open /dev/urandom when it is not true. Otherwise, the file will be reopened every time the application does a DNS lookup, the system will quickly hit the maximum number of file descriptors allowed, and the system will crash because it cannot open any more files.

\n

Finally, a comparison of an embedded system using uClibc before and after adding DNS security patches is given. The following are the DNS packets intercepted by two sniffers. In the first unpatched system, the transaction ID of the DNS request is incremented in sequence, which is an obvious security hole; the second is after the patch is added, the transaction ID of each DNS request is a random value, and the loophole has been filled.

\n

\n","categories":["Technical Know-how"],"tags":["C/C++ Programming","System Programming","Cryptography","Computer Communications","TCP/IP"]}] \ No newline at end of file +[{"title":"AddressSanitizer - A Tool for Programmers to Detect Memory Access Errors","url":"/en/2022/04/22/ASAN-intro/","content":"

Memory access errors are the most common software errors that often cause program crashes. The AddressSanitizer tool, developed by Google engineers in 2012, has become the first choice of C/C++ programmers for its wide coverage, high efficiency, and low overhead. Here is a brief introduction to its principle and usage.

\n

One man's \"magic\" is another man's engineering. \"Supernatural\" is a null word.
Robert Anson Heinlein (American science fiction author, aeronautical engineer, and naval officer)

\n
\n

Tool Overview

\n

The C/C++ language allows programmers to have low-level control over memory, and this direct memory management has made it possible to write efficient application software. However, this has also made memory access errors, including buffer overflows, accesses to freed memory, and memory leaks, a serious problem that must be coped with in program design and implementation. While there are tools and software that provide the ability to detect such errors, their operational efficiency, and functional coverage are often less than ideal.

\n

In 2012, Google engineer Konstantin Serebryany and team members released an open-source memory access error detector for C/C++ programs called AddressSanitizer1. AddressSanitizer (ASan) applies new memory allocation, mapping, and code stubbing techniques to detect almost all memory access errors efficiently. Using the SPEC 2006 benchmark analysis package, ASan runs with an average slowdown of less than 2 and memory consumption of about 2.4 times. In comparison, another well-known detection tool Valgrind has an average slowdown of 20, which makes it almost impossible to put into practice.

\n

The following table summarizes the types of memory access errors that ASan can detect for C/C++ programs:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Error TypeAbbreviationNotes
heap use after freeUAFAccess freed memory (dangling pointer dereference)
heap buffer overflowHeap OOBDynamic allocated memory out-of-bound read/write
heap memory leakHMLDynamic allocated memory not freed after use
global buffer overflowGlobal OOBGlobal object out-of-bound read/write
stack use after scopeUASLocal object out-of-scope access
stack use after returnUARLocal object out-of-scope access after return
stack buffer overflowStack OOBLocal object out-of-bound read/write
\n

ASan itself cannot detect heap memory leaks. But when ASan is integrated into the compiler, as it replaces the memory allocation/free functions, the original leak detection feature of the compiler tool is consolidated with ASan. So, adding the ASan option to the compilation command line also turns on the leak detection feature by default.

\n
\n

This covers all common memory access errors except for \"uninitialized memory reads\" (UMR). ASan detects them with a false positive rate of 0, which is quite impressive. In addition, ASan detects several C++-specific memory access errors such as

\n
    \n
  • Initialization Order Fiasco: When two static objects are defined in different source files and the constructor of one object calls the method of the other object, a program crash will occur if the former compilation unit is initialized first.
  • \n
  • Container Overflow: Given libc++/libstdc++ container, access [container.end(), container.begin() + container.capacity())], which crosses the [container.begin(), container.end()] range but still within the dynamically allocated memory area.
  • \n
  • Delete Mismatch: For the array object created by new foo[n], should not call delete foo for deletion, use delete [] foo instead.
  • \n
\n

ASan's high reliability and performance have made it the preferred choice of compiler and IDE developers since its introduction. Today ASan is integrated into all four major compilation toolsets:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Compiler/IDEFirst Support VersionOSPlatform
Clang/LLVM23.1Unix-likeCross-platform
GCC4.8Unix-likeCross-platform
Xcode7.0Mac OS XApple products
MSVC16.9WindowsIA-32, x86-64 and ARM
\n

ASan's developers first used the Chromium open-source browser for routine testing and found more than 300 memory access errors over 10 months. After integration into mainstream compilation tools, it reported long-hidden bugs in numerous popular open-source software, such as Mozilla Firefox, Perl, Vim, PHP, and MySQL. Interestingly, ASan also identified some memory access errors in the LLVM and GCC compilers' code. Now, many software companies have added ASan run to their mandatory quality control processes.

\n

Working Principle

\n

The USENIX conference paper 3, published by Serebryany in 2012, comprehensively describes the design principles, algorithmic ideas, and programming implementation of ASan. In terms of the overall structure, ASan consists of two parts.

\n
    \n
  1. Compiler instrumentation - modifies the code to verify the shadow memory state at each memory access and creates poisoned red zones at the edges of global and stack objects to detect overflows or underflows.
  2. \n
  3. Runtime library replacement - replaces malloc/free and its related functions to create poisoned red zones at the edge of dynamically allocated heap memory regions, delay the reuse of memory regions after release, and generate error reports.
  4. \n
\n

Here shadow memory, compiler instrumentation, and memory allocation function replacement are all previously available techniques, so how has ASan innovatively applied them for efficient error detection? Let's take a look at the details.

\n

Shadow Memory

\n

Many inspection tools use separated shadow memory to record metadata about program memory, and then apply instrumentation to check the shadow memory during memory accesses to confirm that reads and writes are safe. The difference is that ASan uses a more efficient direct mapping shadow memory.

\n

The designers of ASan noted that typically the malloc function returns a memory address that is at least 8-byte aligned. For example, a request for 20 bytes of memory would divide 24 bytes of memory, with the last 3 bits of the actual return pointer being all zeros. in addition, any aligned 8-byte sequence would only have 9 different states: the first \\(k\\,(0\\leq k \\leq 8)\\) bytes are accessible, and the last \\(8-k\\) are not. From this, they came up with a more compact shadow memory mapping and usage scheme:

\n
    \n
  • Reserve one-eighth of the virtual address space for shadow memory
  • \n
  • Directly map application memory to shadow memory using a formula that divides by 8 plus an offset\n
      \n
    • 32-bit application: Shadow = (Mem >> 3) + 0x20000000;
    • \n
    • 64-bit application: Shadow = (Mem >> 3) + 0x7fff8000;
    • \n
  • \n
  • Each byte of shadow memory records one of the 9 states of the corresponding 8-byte memory block\n
      \n
    • 0 means all 8 bytes are addressable
    • \n
    • Any negative value indicates that the entire 8-byte word is unaddressable (poisoned )
    • \n
    • k (1 ≤ k ≤ 7) means that the first k bytes are addressable
    • \n
  • \n
\n

The following figure shows the address space layout and mapping relationship of ASan. Pay attention to the Bad area in the middle, which is the address segment after the shadow memory itself is mapped. Because shadow memory is not visible to the application, ASan uses a page protection mechanism to make it inaccessible.

\n

\n

Compiler Instrumentation

\n

Once the shadow memory design is determined, the implementation of compiler instrumentation to detect dynamic memory access errors is easy. For memory accesses of 8 bytes, the shadow memory bytes are checked by inserting instructions before the original read/write code, and an error is reported if they are not zero. For memory accesses of less than 8 bytes, the instrumentation is a bit more complicated, where the shadow memory byte values are compared with the last three bits of the read/write address. This situation is also known as the \"slow path\" and the sample code is as follows.

\n
// Check the cases where we access first k bytes of the qword
// and these k bytes are unpoisoned.
bool SlowPathCheck(shadow_value, address, kAccessSize) {
last_accessed_byte = (address & 7) + kAccessSize - 1;
return (last_accessed_byte >= shadow_value);
}
...

byte *shadow_address = MemToShadow(address);
byte shadow_value = *shadow_address;
if (shadow_value) {
if (SlowPathCheck(shadow_value, address, kAccessSize)) {
ReportError(address, kAccessSize, kIsWrite);
}
}
*address = ...; // or: ... = *address;
\n

For global and stack (local) objects, ASan has designed different instrumentation to detect their out-of-bounds access errors. The red zone around a global object is added by the compiler at compile time and its address is passed to the runtime library at application startup, where the runtime library function then poisons the red zone and writes down the address needed in error reporting. The stack object is created at function call time, and accordingly, its red zone is created and poisoned at runtime. In addition, because the stack object is deleted when the function returns, the instrumentation code must also zero out the shadow memory it is mapped to.

\n

In practice, the ASan compiler instrumentation process is placed at the end of the compiler optimization pipeline so that instrumentation only applies to the remaining memory access instructions after variable and loop optimization. In the latest GCC distribution, the ASan compiler stubbing code is located in two files in the gcc subdirectory gcc/asan.[ch].

\n

Runtime Library Replacement

\n

The runtime library needs to include code to manage shadow memory. The address segment to which shadow memory itself is mapped is to be initialized at application startup to disable access to shadow memory by other parts of the program. The runtime library replaces the old memory allocation and free functions and also adds some error reporting functions such as __asan_report_load8.

\n

The newly replaced memory allocation function malloc will allocate additional storage as a red zone before and after the requested memory block and set the red zone to be non-addressable. This is called the poisoning process. In practice, because the memory allocator maintains a list of available memory corresponding to different object sizes, if the list of a certain object is empty, the OS will allocate a large set of memory blocks and their red zones at once. As a result, the red zones of the preceding and following memory blocks will be connected, as shown in the following figure, where \\(n\\) memory blocks require only \\(n+1\\) red zones to be allocated.

\n

\n

The new free function needs to poison the entire storage area and place it in a quarantine queue after the memory is freed. This prevents the memory region from being allocated any time soon. Otherwise, if the memory region is reused immediately, there is no way to detect incorrect accesses to the recently freed memory. The size of the quarantine queue determines how long the memory region is in quarantine, and the larger it is the better its capability of detecting UAF errors!

\n

By default, both the malloc and free functions log their call stacks to provide more detailed information in the error reports. The call stack for malloc is kept in the red zone to the left of the allocated memory, so a large red zone can retain more call stack frames. The call stack for free is stored at the beginning of the allocated memory region itself.

\n

Integrated into the GCC compiler, the source code for the ASan runtime library replacement is located in the libsanitizer subdirectory libsanitizer/asan/*, and the resulting runtime library is compiled as libasan.so.

\n

Application Examples

\n

ASan is very easy to use. The following is an example of an Ubuntu Linux 20.4 + GCC 9.3.0 system running on an x86_64 virtual machine to demonstrate the ability to detect various memory access errors.

\n

Test Cases

\n

As shown below, the test program writes seven functions, each introducing a different error type. The function names are cross-referenced with the error types one by one:

\n
/*
* PakcteMania https://www.packetmania.net
*
* gcc asan-test.c -o asan-test -fsanitize=address -g
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
/* #include <sanitizer/lsan_interface.h> */

int ga[10] = {1};

int global_buffer_overflow() {
return ga[10];
}

void heap_leak() {
int* k = (int *)malloc(10*sizeof(int));
return;
}

int heap_use_after_free() {
int* u = (int *)malloc(10*sizeof(int));
u[9] = 10;
free(u);
return u[9];
}

int heap_buffer_overflow() {
int* h = (int *)malloc(10*sizeof(int));
h[0] = 10;
return h[10];
}

int stack_buffer_overflow() {
int s[10];
s[0] = 10;
return s[10];
}

int *gp;

void stack_use_after_return() {
int r[10];
r[0] = 10;
gp = &r[0];
return;
}

void stack_use_after_scope() {
{
int c = 0;
gp = &c;
}
*gp = 10;
return;
}
\n

The test program calls the getopt library function to support a single-letter command line option that allows the user to select the type of error to be tested. The command line option usage information is as follows.

\n
\b$ ./asan-test

Test AddressSanitizer
usage: asan-test [ -bfloprs ]

-b\theap buffer overflow
-f\theap use after free
-l\theap memory leak
-o\tglobal buffer overflow
-p\tstack use after scope
-r\tstack use after return
-s\tstack buffer overflow
\n

The GCC compile command for the test program is simple, just add two compile options

\n
    \n
  • -fsanitize=address: activates the ASan tool
  • \n
  • -g: enable debugging and keep debugging information
  • \n
\n

OOB Test

\n

For Heap OOB error, the run result is

\n
$ ./asan-test -b
=================================================================
==57360==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x604000000038 at pc 0x55bf46fd64ed bp 0x7ffced908dc0 sp 0x7ffced908db0
READ of size 4 at 0x604000000038 thread T0
#0 0x55bf46fd64ec in heap_buffer_overflow /home/zixi/coding/asan-test.c:34
#1 0x55bf46fd6a3f in main /home/zixi/coding/asan-test.c:88
#2 0x7fd16f6560b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x55bf46fd628d in _start (/home/zixi/coding/asan-test+0x128d)

0x604000000038 is located 0 bytes to the right of 40-byte region [0x604000000010,0x604000000038)
allocated by thread T0 here:
#0 0x7fd16f92ebc8 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
#1 0x55bf46fd646c in heap_buffer_overflow /home/zixi/coding/asan-test.c:32
#2 0x55bf46fd6a3f in main /home/zixi/coding/asan-test.c:88
#3 0x7fd16f6560b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/zixi/coding/asan-test.c:34 in heap_buffer_overflow
Shadow bytes around the buggy address:
0x0c087fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c087fff8000: fa fa 00 00 00 00 00[fa]fa fa fa fa fa fa fa fa
0x0c087fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
...
==57360==ABORTING
\n

Referring to the heap-buffer-overflow function implementation, you can see that it requests 40 bytes of memory to hold 10 32-bit integers. However, on the return of the function, the code overruns to read the data after the allocated memory. As the above run log shows, the program detects a Heap OOB error and aborts immediately. ASan reports the name of the source file and line number asan-test.c:34 where the error occurred, and also accurately lists the original allocation function call stack for dynamically allocated memory. The \"SUMMARY\" section of the report also prints the shadow memory data corresponding to the address in question (observe the lines marked by =>). The address to be read is 0x604000000038, whose mapped shadow memory address 0x0c087fff8007 holds the negative value 0xfa (poisoned and not addressable). Because of this, ASan reports an error and aborts the program.

\n

The Stack OOB test case is shown below. ASan reports an out-of-bounds read error for a local object. Since the local variables are located in the stack space, the starting line number asan-test.c:37 of the function stack_buffr_overflow is listed. Unlike the Heap OOB report, the shadow memory poisoning values for the front and back redzone of the local variable are different, with the previous Stack left redzone being 0xf1 and the later Stack right redzone being 0xf3. Using different poisoning values (both negative after 0x80) helps to quickly distinguish between the different error types.

\n
$ ./asan-test -s
=================================================================
==57370==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7f1cf5044058 at pc 0x55d8b7e9d601 bp 0x7ffc830c29e0 sp 0x7ffc830c29d0
READ of size 4 at 0x7f1cf5044058 thread T0
#0 0x55d8b7e9d600 in stack_buffer_overflow /home/zixi/coding/asan-test.c:40
#1 0x55d8b7e9daec in main /home/zixi/coding/asan-test.c:108
#2 0x7f1cf87760b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x55d8b7e9d28d in _start (/home/zixi/coding/asan-test+0x128d)

Address 0x7f1cf5044058 is located in stack of thread T0 at offset 88 in frame
#0 0x55d8b7e9d505 in stack_buffer_overflow /home/zixi/coding/asan-test.c:37

This frame has 1 object(s):
[48, 88) 's' (line 38) <== Memory access at offset 88 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/zixi/coding/asan-test.c:40 in stack_buffer_overflow
Shadow bytes around the buggy address:
0x0fe41ea007b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea007c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea007d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea007e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea007f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0fe41ea00800: f1 f1 f1 f1 f1 f1 00 00 00 00 00[f3]f3 f3 f3 f3
0x0fe41ea00810: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea00820: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea00830: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea00840: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe41ea00850: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
...
==57370==ABORTING
\n

The following Global OOB test result also clearly shows the error line asan-test.c:16, the global variable name ga and its definition code location asan-test.c:13:5, and you can also see that the global object has a red zone poisoning value of 0xf9.

\n
$ ./asan-test -o
=================================================================
==57367==ERROR: AddressSanitizer: global-buffer-overflow on address 0x564363ea4048 at pc 0x564363ea1383 bp 0x7ffc0d6085d0 sp 0x7ffc0d6085c0
READ of size 4 at 0x564363ea4048 thread T0
#0 0x564363ea1382 in global_buffer_overflow /home/zixi/coding/asan-test.c:16
#1 0x564363ea1a6c in main /home/zixi/coding/asan-test.c:98
#2 0x7f8cb43890b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x564363ea128d in _start (/home/zixi/coding/asan-test+0x128d)

0x564363ea4048 is located 0 bytes to the right of global variable 'ga' defined in 'asan-test.c:13:5' (0x564363ea4020) of size 40
SUMMARY: AddressSanitizer: global-buffer-overflow /home/zixi/coding/asan-test.c:16 in global_buffer_overflow
Shadow bytes around the buggy address:
0x0ac8ec7cc7b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc7c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc7d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc7e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc7f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0ac8ec7cc800: 00 00 00 00 00 00 00 00 00[f9]f9 f9 f9 f9 f9 f9
0x0ac8ec7cc810: 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
0x0ac8ec7cc820: f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9
0x0ac8ec7cc830: f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 f9 00 00 00 00
0x0ac8ec7cc840: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0ac8ec7cc850: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
...
==57367==ABORTING
\n

Note that in this example, the global array int ga[10] = {1}; is initialized, what happens if it is uninitialized? Change the code slightly

\n
int ga[10];

int global_buffer_overflow() {
ga[0] = 10;
return ga[10];
}
\n

Surprisingly, ASan does not report the obvious Global OOB error here. Why?

\n

The reason has to do with the way GCC treats global variables. The compiler treats functions and initialized variables as Strong symbols, while uninitialized variables are Weak symbols by default. Since the definition of weak symbols may vary from source file to source file, the size of the space required is unknown. The compiler cannot allocate space for weak symbols in the BSS segment, so it uses the COMMON block mechanism so that all weak symbols share a COMMON memory region, thus ASan cannot insert the red zone. During the linking process, after the linker reads all the input target files, it can determine the size of the weak symbols and allocate space for them in the BSS segment of the final output file.

\n

Fortunately, GCC's -fno-common option turns off the COMMON block mechanism, allowing the compiler to add all uninitialized global variables directly to the BSS segment of the target file, also allowing ASan to work properly. This option also disables the linker from merging weak symbols, so the linker reports an error directly when it finds a compiled unit with duplicate global variables defined in the target file.

\n

This is confirmed by a real test. Modify the GCC command line for the previous code segment

\n
gcc asan-test.c -o asan-test -fsanitize=address -fno-common -g
\n

then compile, link, and run. ASan successfully reported the Global OOB error.

\n

UAF Test

\n

The following is a running record of UAF error detection. Not only is the information about the code that went wrong reported here, but also the call stack of the original allocation and free functions of the dynamic memory is given. The log shows that the memory was allocated by asan-test.c:25, freed at asan-test.c:27, and yet read at asan-test.c:28. The shadow memory data printed later indicates that the data filled is negative 0xfd, which is also the result of the poisoning of the memory after it is freed.

\n
$ \u0007./asan-test -\bf
=================================================================
==57363==ERROR: AddressSanitizer: heap-use-after-free on address 0x604000000034 at pc 0x558b4a45444e bp 0x7ffccf4ca790 sp 0x7ffccf4ca780
READ of size 4 at 0x604000000034 thread T0
#0 0x558b4a45444d in heap_use_after_free /home/zixi/coding/asan-test.c:28
#1 0x558b4a454a4e in main /home/zixi/coding/asan-test.c:91
#2 0x7fc7cc98b0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x558b4a45428d in _start (/home/zixi/coding/asan-test+0x128d)

0x604000000034 is located 36 bytes inside of 40-byte region [0x604000000010,0x604000000038)
freed by thread T0 here:
#0 0x7fc7ccc637cf in __interceptor_free (/lib/x86_64-linux-gnu/libasan.so.5+0x10d7cf)
#1 0x558b4a454412 in heap_use_after_free /home/zixi/coding/asan-test.c:27
#2 0x558b4a454a4e in main /home/zixi/coding/asan-test.c:91
#3 0x7fc7cc98b0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

previously allocated by thread T0 here:
#0 0x7fc7ccc63bc8 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
#1 0x558b4a4543bd in heap_use_after_free /home/zixi/coding/asan-test.c:25
#2 0x558b4a454a4e in main /home/zixi/coding/asan-test.c:91
#3 0x7fc7cc98b0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

SUMMARY: AddressSanitizer: heap-use-after-free /home/zixi/coding/asan-test.c:28 in heap_use_after_free
Shadow bytes around the buggy address:
0x0c087fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c087fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c087fff8000: fa fa fd fd fd fd[fd]fa fa fa fa fa fa fa fa fa
0x0c087fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c087fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
...
==57363==ABORTING
\n

HML Test

\n

The results of the memory leak test are as follows. Unlike the other test cases, ABORTING is not printed at the end of the output record. This is because, by default, ASan only generates a memory leak report when the program terminates (process ends). If you want to check for leaks on the fly, you can call ASan's library function __lsan_do_recoverable_leak_check, whose definition is located in the header file sanitizer/lsan_interface.h.

\n
$ ./asan-test -l
=================================================================
==57365==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 40 byte(s) in 1 object(s) allocated from:
#0 0x7f06b85b1bc8 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10dbc8)
#1 0x5574a8bcd3a0 in heap_leak /home/zixi/coding/asan-test.c:20
#2 0x5574a8bcda5d in main /home/zixi/coding/asan-test.c:94
#3 0x7f06b82d90b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

SUMMARY: AddressSanitizer: 40 byte(s) leaked in 1 allocation(s).
\n

UAS Test

\n

See the stack_use_after_scope function code, where the memory unit holding the local variable c is written outside of its scope. The test log accurately reports the line number line 54 where the variable is defined and the location of the incorrect writing code asan-test.c:57:

\n
./asan-test -\bp
=================================================================
==57368==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7f06f0a9b020 at pc 0x56121a7548d9 bp 0x7ffd1de0d050 sp 0x7ffd1de0d040
WRITE of size 4 at 0x7f06f0a9b020 thread T0
#0 0x56121a7548d8 in stack_use_after_scope /home/zixi/coding/asan-test.c:57
#1 0x56121a754a7b in main /home/zixi/coding/asan-test.c:101
#2 0x7f06f42cd0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#3 0x56121a75428d in _start (/home/zixi/coding/asan-test+0x128d)

Address 0x7f06f0a9b020 is located in stack of thread T0 at offset 32 in frame
#0 0x56121a7547d0 in stack_use_after_scope /home/zixi/coding/asan-test.c:52

This frame has 1 object(s):
[32, 36) 'c' (line 54) <== Memory access at offset 32 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope /home/zixi/coding/asan-test.c:57 in stack_use_after_scope
Shadow bytes around the buggy address:
0x0fe15e14b5b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b5c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b5d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b5e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b5f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0fe15e14b600: f1 f1 f1 f1[f8]f3 f3 f3 00 00 00 00 00 00 00 00
0x0fe15e14b610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b630: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe15e14b650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
...
==57368==ABORTING
\n

UAR Test

\n

The UAR test has its peculiarities. Because the stack memory of a function is reused immediately after it returns, to detect local object access errors after return, a \"pseudo-stack\" of dynamic memory allocation must be set up, for details check the relevant Wiki page of ASan4. Since this algorithm change has some performance impact, ASan does not detect UAR errors by default. If you really need to, you can set the environment variable ASAN_OPTIONS to detect_stack_use_after_return=1 before running. The corresponding test logs are as follows.

\n
$ export ASAN_OPTIONS=detect_stack_use_after_return=1
$ env | grep ASAN
ASAN_OPTIONS=detect_stack_use_after_return=1
$ ./asan-test -\br
=================================================================
==57369==ERROR: AddressSanitizer: stack-use-after-return on address 0x7f5493e93030 at pc 0x55a356890ac9 bp 0x7ffd22c5cf30 sp 0x7ffd22c5cf20
READ of size 4 at 0x7f5493e93030 thread T0
#0 0x55a356890ac8 in main /home/zixi/coding/asan-test.c:105
#1 0x7f54975c50b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#2 0x55a35689028d in _start (/home/zixi/coding/asan-test+0x128d)

Address 0x7f5493e93030 is located in stack of thread T0 at offset 48 in frame
#0 0x55a356890682 in stack_use_after_return /home/zixi/coding/asan-test.c:45

This frame has 1 object(s):
[48, 88) 'r' (line 46) <== Memory access at offset 48 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-return /home/zixi/coding/asan-test.c:105 in main
Shadow bytes around the buggy address:
0x0feb127ca5b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca5c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca5d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca5e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca5f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0feb127ca600: f5 f5 f5 f5 f5 f5[f5]f5 f5 f5 f5 f5 f5 f5 f5 f5
0x0feb127ca610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca630: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0feb127ca650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
...
==57369==ABORTING
\n

ASan supports many other compiler flags and runtime environment variable options to control and tune the functionality and scope of the tests. For those interested please refer to the ASan flags Wiki page5.

\n

A zip archive of the complete test program is available for download here: asan-test.c.gz

\n
\n
\n
    \n
  1. AddressSanitizer Wiki↩︎

  2. \n
  3. Clang 13 documentation: ADDRESSSANITIZER↩︎

  4. \n
  5. Serebryany, K.; Bruening, D.; Potapenko, A.; Vyukov, D. \"AddressSanitizer: a fast address sanity checker\". In USENIX ATC, 2012↩︎

  6. \n
  7. AddressSanitizerUseAfterReturn↩︎

  8. \n
  9. AddressSanitizerFlags↩︎

  10. \n
\n
\n","categories":["Tool Guide"],"tags":["C/C++ Programming","System Programming"]},{"title":"Programming in C Exam Review and Practices (I)","url":"/en/2024/02/28/C-Prog-Exam-Review-Practices-1/","content":"

Here is a series of general study guides to college-level C programming courses. This is the first part covering compilation and linking, file operations, typedef, structures, string operations, basic pointer operations, etc.

\n

Compilation and Linking

\n
    \n
  • Write the command to compile a single C file named \"hello.c\" into an object file called \"hello.o\".

    \n

    gcc -c hello.c -o hello.o

  • \n
  • Write the command to link two object files named \"hello.o\" and \"goodbye.o\" into the executable called \"application\".

    \n

    gcc hello.o goodbye.o -o application

  • \n
  • Can you \"run\" an object file if it contains the \"main()\" function?

    \n

    No, an object file cannot be run directly. If you force it to run, it will exec format error.

  • \n
  • Can you \"run\" an executable that contains a single function called \"main()\"?

    \n

    Yes, an executable with just main() can be run.

  • \n
  • Can you \"run\" an executable that does not contain a function called \"main()\"?

    \n

    No, main() is required to run an executable.

  • \n
  • What does the \"-Wall\" flag do?

    \n

    \"-Wall\" enables all compiler warnings

  • \n
  • What does the \"-g\" flag do?

    \n

    \"-g\" adds debugging information.

  • \n
  • What does the \"-ansi\" flag do?

    \n

    \"-ansi\" enables strict ANSI C mode. The \"-ansi\" flag is equivalent to the -\"std=c89\" flag.

  • \n
  • What does the \"-c\" flag do?

    \n

    \"-c\" compiles to object file only, does not link.

  • \n
  • What does the \"-o\" flag do?

    \n

    \"-o\" specifies output file name.

    \n
      \n
    • If \"-c\" is also used with a single [filename].c file, and no other .o in the command line, gcc will default generate an object file named [filename].o. If \"-o\" is used in such a case, it will create an object file with the specified name.
    • \n
    • If no \"-c\" is used, gcc will by default create an executable file named \"a.out\".
    • \n
  • \n
\n

File Operations

\n
    \n
  • Given the following FILE pointer variable definition, write the code that will open a file named \"hello.txt\" for read-only access and print a message of your choice if there was an error in doing so.

    \n

    FILE *my_file = 0;

    \n

    my_file = fopen("hello.txt", "r");
    if (my_file = NULL) {
    fprintf(stdout, "Failed to open the file\\n");
    }

  • \n
  • Write code that will, without opening any file, check if a file named \"hello.txt\" can be opened for read access. Put the code inside the 'if' predicate:

    \n

    if (access("hello.txt", R_OK) == 0) {
    /* Yes, we can open the file... */
    }

  • \n
  • Write code that will, without opening any file, check if a file named \"hello.txt\" can be opened for write access. Put the code inside the 'if' predicate:

    \n

    if (access("hello.txt", W_OK) == 0) {
    /* Yes, we can open the file... */
    }

  • \n
  • Write a function called read_and_print() that will do the following:

    \n
      \n
    • Open a text file called \"hello.txt\" for read-only access.
    • \n
    • Read a word that is terminated by a newline from the file into the character array called \"my_string\".
    • \n
    • Read an integer terminated by a newline into the int variable called \"my_int\".
    • \n
    • Print the string and the integer value.
    • \n
    • Return the my_int value.
    • \n
    • If the file cannot be opened for reading, return -1.
    • \n
    • If an error occurs while reading from the file, return -1.
    • \n
    \n

    int read_and_print() {
    char my_string[100];
    my_int;

    FILE *fp = fopen("hello.txt", "r");
    if(!fp) return -1;

    if (fscanf(fp, "%s", my_string) != 1) {
    fclose(fp);
    fp = NULL;
    return -1;
    }
    if (fscanf(fp, "%d", &my_int) != 1) {
    fclose(fp);
    fp = NULL;
    return -1;
    }
    printf("%s %d\\n", my_string, my_int);
    fclose(fp);
    fp = NULL;
    return my_int;
    }

  • \n
  • Write a function named print_reverse that will open a text file named \"hello.txt\" and print each character in the file in reverse. i.e. print the first character last and the last character first. The function should return the number of characters in the file. Upon any error, return -1. HINT: Use fseek() a lot to do this.

    \n

    int print_reverse(char* filename) {

    FILE* fp = fopen(filename, "r");
    if(!fp) return -1;

    fseek(fp, 0, SEEK_END);
    int size = ftell(fp);

    for (int i = size - 1; i >= 0; i--) {
    fseek(fp, i, SEEK_SET);
    char c = fgetc(fp);
    printf("%c", c);
    }

    fclose(fp);
    fp = NULL;
    return size;
    }

  • \n
  • Write a function that defines a structure, initializes it, writes it to a file called \"struct.out\", closes the file, re-opens the file for read-only access, reads a single structure into a new struct variable, and then closes the file. Print the structure contents to the screen. On any error, return -1. Otherwise, return 0.

    \n

    #include <stdio.h>

    struct Person {
    char name[50];
    int age;
    };

    int write_and_read_struct() {
    struct Person p = { "John Doe", 30 };

    // Write struct to file
    FILE* fp = fopen("struct.out", "w");
    if (!fp) return -1;

    if (fwrite(&p, sizeof(struct Person), 1, fp) != 1) {
    \tfclose(fp);
    \tfp = NULL:
    \treturn -1;
    }
    fclose(fp);

    // Read struct from file
    fp = fopen("struct.out", "r");
    if (!fp) return -1;

    struct Person p2;
    if (fread(&p2, sizeof(struct Person), 1, fp) != 1) {
    fclose(fp);
    fp = NULL;
    return -1;
    }
    fclose(fp);
    fp = NULL;

    // Print struct
    printf("Name: %s, Age: %d\\n", p2.name, p2,age);
    return 0;
    }

  • \n
\n

Typedef

\n
    \n
  • Declare a type called \"my_array_t\" that is an array of 15 floats.

    \n

    typedef float my_array_t[15];

  • \n
  • Declare a type called \"struct_arr_t\" that is an array of 10 structs of the format

    \n

    struct str {
    int x;
    int y;
    };

    \n

    typedef struct str struct_arr_t[10];

  • \n
  • Define a variable called my_str_arr of type struct_arr_type.

    \n

    struct_arr_t my_str_arr;

  • \n
\n

Structures

\n
    \n
  • Can two elements within a structure have the same name?

    \n

    No, two elements cannot have the same name

  • \n
  • Can you initialize a structure like this?

    \n

    struct my_str {
    int x;
    float y;
    } mine = { 0, 0.0 };
    Yes, you can initialize it like that.

  • \n
  • Can you initialize a structure like this?

    \n

    struct my_str {
    int x;
    float y;
    };
    void my_func(int n) {
    my_str mine = { n, 0.0 };
    }
    No, here my_str is not a type. To fix this, use struct str mine = { n, 0.0 }; instead.

  • \n
  • Declare a structure that contains an integer element named i, a floating point element named f, and an array of 20 characters named str (in that order). Name it anything you want.

    \n

    struct mystruct {
    int i;
    float f;
    char str[20];
    };

  • \n
  • Define a variable called \"my_new_struct\" of the type in the previous question.

    \n

    struct mystruct my_new_struct;

  • \n
  • Define a variable called \"my_array_of_structs\" that is an array of 40 structures of the type in the prior two questions.

    \n

    struct mystruct my_array_of_structs[40];

  • \n
  • Define a function called bigger_rectangle() that will accept one argument of the structure type rectangle (declared below) and will multiply the width dimension by 1.5, the height dimension by 2.5 and the length dimension by 3. The function should return the new structure. Define a temporary local variable if you want to.

    \n

    struct rectangle {
    float height;
    float width;
    float length;
    };

    \n

    struct rectangle bigger_rectangle(struct rectangle r) {
    struct rectangle bigger;
    bigger.height = r.height * 2.5;
    bigger.width = r.width * 1.5;
    bigger.length = r.length * 3;
    return bigger;
    }

  • \n
  • Write a function named sum_rectangles that will open a binary file named \"rect.in\" for reading and read the binary images of rectangle structures from it. For each rectangle structure, add its elements to those of the first structure read. e.g. sum the height fields of all the structures, sum the width fields of all the structures, etc... Return a structure from sum_rectangles where each element represents the sum of all structures read from the file. i.e. the height field should be the sum of all of the height fields of each of the structures. On any file error, return the structure { -1.0, -1.0, -1.0 }.

    \n

    #include <stdio.h>

    struct rectangle {
    float height;
    float width;
    float length;
    };

    struct rectangle sum_rectangles() {
    struct rectangle bad_struct = {-1.0, -1.0, -1.0};

    FILE *fp = fopen("rect.in", "rb");
    if(!fp) {
    return bad_struct;
    }

    struct rectangle sum = {0, 0, 0};
    struct rectangle r;

    if (fread(&r, sizeof(struct rectangle), 1, fp) != 1) {
    fclose(fp);
    fp = NULL;
    return bad_struct;
    }

    sum.height = r.height;
    sum.width = r.width;
    sum.length = r.length;

    while (fread(&r, sizeof(struct rectangle), 1, fp) == 1) {
    sum.height += r.height;
    sum.width += r.width;
    sum.length += r.length;
    }

    fclose(fp);
    fp = NULL;
    return sum;
    }

  • \n
\n

assert()

\n
    \n
  • Under what circumstances would you place an assert() into your code?

    \n

    Used to check for logical errors and malformed data.

  • \n
  • What will be the result of the following code:

    \n

    int my_func() {
    int count = 0;
    int sum = 0;

    for (count = 0; count < 100; count++) {
    assert(sum > 0);
    sum = sum + count;
    }
    return sum;
    }
    The program will abort/crash on the assert line.

  • \n
  • What might you do to the previous code to make it do a \"better\" job?

    \n

    Move assert(sum > 0); down, after for loop. Or change to assert(sum >= 0);

  • \n
\n

String Operations

\n
    \n
  • Write a function called do_compare() that will prompt the user for two strings of maximum length 100. It should compare them and print one of the following messages:

    \n
      \n
    • The strings are equal.
    • \n
    • The first string comes before the second.
    • \n
    • The second string comes before the first.
    • \n
    \n

    The function should always return zero.

    \n

    #include <stdio.h>
    #include <string.h>

    int do_compare() {
    char str1[101], str2[101];

    // Prompt the user to enter two strings
    printf("Enter the first string (up to 100 characters): ");
    fgets(str1, sizeof(str1), stdin);

    printf("Enter the second string (up to 100 characters): ");
    fgets(str2, sizeof(str2), stdin);

    // Compare the strings
    int cmp = strcmp(str1, str2);

    // Print the comparison result
    if (cmp == 0) {
    printf("The strings are equal.\\n");
    } else if (cmp < 0) {
    printf("The first string comes before the second.\\n");
    } else {
    printf("The second string comes before the first.\\n");
    }

    return 0;
    }

  • \n
\n

Variables

\n
    \n
  • What is the difference between initialization of a variable and assignment to a variable?

    \n

    Initialization is giving a variable its initial value, typically at the time of declaration, while assignment is giving a new value to an already declared variable at any point after initialization.

  • \n
  • What is the difference between a declaration and a definition?

    \n

    Declaration is announcing the properties of var (no memory allocation), definition is allocating storage for a var and initializing it.

  • \n
  • What is the difference between a global variable and a local variable?

    \n

    Global variables have a broader scope, longer lifetime, and higher visibility compared to local variables, which are limited to the scope of the function in which they are declared.

  • \n
  • For the following questions, assume that the size of an 'int' is 4 bytes, the size of a 'char' is one byte, the size of a 'float' is 4 bytes, and the size of a 'double' is 8 bytes. Write the size of the following expressions:

    \n

    struct my_coord {
    int x;
    int y;
    double altitude;
    };

    struct my_line {
    struct my_coord first;
    struct my_coord second;
    char name[10];
    };

    struct my_coord var;
    struct my_coord array[3];
    struct my_line one_line;
    struct my_line two_lines[2];

    \n

    sizeof(struct my_coord) = __16___

    \n

    sizeof(var) = __16___

    \n

    sizeof(array[1]) = __16___

    \n

    sizeof(array[2]) = __16___

    \n

    sizeof(array) = __48___

    \n

    sizeof(struct my_line) = __48___

    \n

    sizeof(two_lines) = __96___

    \n

    sizeof(one_line) = __48___

    \n

    Explanation: When calculating the size of a struct, we need to consider alignment and padding, which can affect the overall size of the struct. In the case of struct my_line, the total size is influenced by the alignment requirements of its members. The largest member of struct my_coord is double altitude, which is 8 bytes. This means that the double altitude member will determine the alignment and padding for the entire struct my_coord within struct my_line.

    \n

    So here char name[10]; will occupy (10 bytes) + (6 bytes padding to align char[10] on an 8-byte boundary). This ends up with (16+16+10+6) for the size of struct my_line.

    \n

    Remember that the size of the structure should be a multiple of the biggest variable.

  • \n
  • Draw the memory layout of the prior four variables; var, array, one_line, and two_lines on a line of boxes. Label the start of each variable and clearly show how many bytes each element within each structure variable consumes.

  • \n
  • Re-define the two_lines variable above and _initialize_ it's contents with the following values:

    \n

    first my_line structure:
    first my_coord structure:
    x = 1
    y = 3
    altitude = 5.6
    second my_coord structure:
    x = 4
    y = 5
    altitude = 2.1
    name = "My Town"
    second my_line structure:
    first my_coord structure:
    x = 9
    y = 2
    altitude = 1.1
    second my_coord structure:
    x = 3
    y = 3
    altitude = 0.1
    name = "Your Town"

    \n

    struct my_line two_lines[2] = {
    {
    {1, 3, 5.6},
    {4, 5, 2.1},
    "My Town"
    },
    {
    {9, 2, 1.1},
    {3, 3, 0.1},
    "Your Town"
    }
    };

  • \n
  • How many bytes large is the following definition?

    \n
    struct my_coord new_array[] = {
    { 0,0,3.5 },
    { 1,2,4.5},
    { 2,0,9.5}
    };
    \n

    (4 + 4 + 8) * 3 = 48

  • \n
\n

Basic Pointer Operations

\n
    \n
  • What is printed by the following three pieces of code:

    \n

    int x = 0;                int x = 0;                int x = 0;
    int y = 0; int y = 0; int y = 0;
    int *p = NULL; int *p = NULL; int *p = NULL;
    int *q = NULL; int *q = NULL;
    p = &x;
    *p = 5; p = &x; p = &y;
    p = &y; q = p; q = &x;
    *p = 7; *q = 7; p = 2;
    q = 3;
    printf("%d %d\\n", x, y); printf("%d %d\\n", x, y); printf("%d %d\\n", x, y);

    \n

    The 1st column code snippet printed 5 7. The 1st column code snippet printed 7 0. The 1st column code snippet printed 0 0.

  • \n
  • Consider the following variable definitions:

    \n
    int x = 2;
    int arr[10] = {4, 5, 6, 7, 1, 2, 3, 0, 8, 9};
    int *p;
    \n

    And assume that p is initialized to point to one of the integers in arr. Which of the following statements are legitimate? Why or why not?

    \n

    p = arr;      arr = p;      p = &arr[2];   p = arr[x];     p = &arr[x];

    arr[x] = p; arr[p] = x; &arr[x] = p; p = &arr; x = *arr;

    x = arr + x; p = arr + x; arr = p + x; x = &(arr+x); p++;

    x = --p; x = *p++; x = (*p)++; arr++; x = p - arr;

    x = (p>arr); arr[*p]=*p; *p++ = x; p = p + 1; arr = arr + 1;

    \n

    Let's go through each statement to determine if it is legitimate or not, and explain:

    \n
      \n
    • p = arr; - Legitimate. Assigns the address of the first element of arr to p.
    • \n
    • arr = p; - Not legitimate. You cannot assign to an array name.
    • \n
    • p = &arr[2]; - Legitimate. Assigns the address of arr[2] to p.
    • \n
    • p = arr[x]; - Not legitimate. arr[x] is an integer value, not an address.
    • \n
    • p = &arr[x]; - Legitimate. Assigns the address of arr[x] to p.
    • \n
    • arr[x] = p; - Not legitimate. arr[x] is an integer value, not a pointer.
    • \n
    • arr[p] = x; - Not legitimate. arr[p] is not a valid operation. p should be an index, not a pointer.
    • \n
    • &arr[x] = p; - Not legitimate. You cannot assign a value to the address of an element.
    • \n
    • p = &arr; - Not legitimate. &arr is the address of the whole array, not a pointer to an integer.
    • \n
    • x = *arr; - Legitimate. Assigns the value of the first element of arr to x.
    • \n
    • x = arr + x; - Legitimate. Calculates the address of arr[x] and assigns it to x.
    • \n
    • p = arr + x; - Legitimate. Calculates the address of arr[x] and assigns it to p.
    • \n
    • arr = p + x; - Not legitimate. You cannot assign to an array name.
    • \n
    • x = &(arr+x); - Not legitimate. & expects an lvalue, but (arr+x) is not an lvalue.
    • \n
    • p++; - Legitimate. Increments the pointer p to point to the next element.
    • \n
    • x = --p; - Legitimate. Decrements p and assigns its value to x.
    • \n
    • x = *p++; - Legitimate. Assigns the value pointed to by p to x, then increments p.
    • \n
    • x = (*p)++; - Legitimate. Assigns the value pointed to by p to x, then increments the value pointed to by p.
    • \n
    • arr++; - Not legitimate. You cannot increment the entire array arr.
    • \n
    • x = p - arr; - Legitimate. Calculates the difference in addresses between p and arr and assigns it to x.
    • \n
    • x = (p>arr); - Not legitimate. Comparison between a pointer and an array is not valid.
    • \n
    • arr[*p]=*p; - Not legitimate. arr[*p] is not a valid assignment target.
    • \n
    • *p++ = x; - Legitimate. Assigns x to the value pointed to by p, then increments p.
    • \n
    • p = p + 1; - Legitimate. Increments the pointer p to point to the next memory location.
    • \n
    • arr = arr + 1; - Not legitimate. You cannot increment the entire array arr.
    • \n
    \n

    📝Notes: The difference between x = *p++; and x = (*p)++; lies in how the increment operator (++) is applied.

    \n

    \n
      \n
    • x = *p++; This statement first dereferences the pointer p to get the value it points to, assigns that value to x and then increments the pointer p to point to the next element (not the value pointed to by p). So, x gets the value pointed to by p before the increment.
    • \n
    • x = (*p)++; This statement first dereferences the pointer p to get the value it points to, assigns that value to x, and then increments the value pointed to by p. So, x gets the value pointed to by p before the increment, and the value at the memory location pointed to by p is incremented.
    • \n
    \n

    Here's a brief example to illustrate the difference:

    \n

    #include <stdio.h>

    int main() {
    int array[] = {1, 2, 3};
    int *p = array;
    int x;

    // x gets the value pointed to by p, then p is incremented
    x = *p++; // x = 1, p now points to array[1]
    printf("x = %d, array[1] = %d, p points to %d\\n", x, array[1], *p);

    // x gets the value pointed to by p, then the value pointed to
    // by p is incremented
    x = (*p)++; // x = 2, array[1] is now 3
    printf("x = %d, array[1] = %d, p points to %d\\n", x, array[1], *p);
    return 0;
    }

    \n

    The output of the above program is

    \n

    x = 1, array[1] = 2, p points to 2
    x = 2, array[1] = 3, p points to 3

    \n

    To test your understanding, now check the following code snippet, what will the output be:

    \n

    int x = 2, y = 15, z = 0;
    int *p = 0;

    p = &y;
    x = *p++;
    printf("x = %d, y = %d, z = %d\\n", x, y, z);

    p = &y;
    z = (*p)++;
    printf("x = %d, y = %d, z = %d\\n", x, y, z);

    \n

    Answer

    \n
    \n
    x = 15, y = 15, z = 0
    x = 15, y = 16, z = 15
    \n

    So the variable y has its value incremented after z = (*p)++;.

    \n\n

  • \n
  • Given the following definitions:

    \n

    int arr[] = { 0, 1, 2, 3 };
    int *p = arr;
    are the following two statements equivalent?

    \n

    p = p + 1;
    p++;
    What can you say about the result of adding a pointer to an integer?

    \n

    Yes, the two statements p = p + 1; and p++; are equivalent in this context. Both statements increment the pointer p to point to the next element in the array arr.

    \n

    In general, if ptr is a pointer to type T, then ptr + n will point to the memory location \"ptr + n * sizeof(T)\". This is useful for iterating over arrays or accessing elements in memory sequentially.

  • \n
  • Write a function called 'swap' that will accept two pointers to integers and will exchange the contents of those integer locations.

    \n
      \n
    • Show a call to this subroutine to exchange two variables.

      \n

      Here is the sample code:

      \n

      #include <stdio.h>

      void swap(int *a, int *b) {
      int temp = *a;
      *a = *b;
      *b = temp;
      }

      int main() {
      int x = 5, y = 10;

      printf("Before: x = %d, y = %d\\n", x, y);
      swap(&x, &y);
      printf("After: x = %d, y = %d\\n", x, y);
      return 0;
      }

    • \n
    • Why is it necessary to pass pointers to the integers instead of just passing the integers to the Swap subroutine?

      \n

      It is necessary to pass pointers to the integers instead of just passing the integers themselves to the swap subroutine because C passes arguments by value. When you pass an integer to a function, a copy of the integer's value is made and passed to the function. Any changes made to the parameter inside the function do not affect the original variable outside the function.

      \n

      By passing pointers to integers (int *a and int *b), you are passing the memory addresses of the integers. This allows the swap function to access and modify the actual integers in memory, rather than working with copies. As a result, the values of the integers are swapped correctly, and the changes are reflected outside the function.

      \n

      In summary, passing pointers to integers allows the swap function to modify the values of the integers themselves, rather than just copies of the values.

    • \n
    • What would happen if you called swap like this:

      \n

      int x = 5;
      swap(&x, &x);

      \n

      If you called swap(&x, &x); with the same pointer &x for both arguments, it would effectively try to swap the contents of x with itself. The result would be that x would remain unchanged, as the swap operation would effectively cancel itself out. The swap operation had no net effect on x.

    • \n
    • Can you do this: (why or why not?)

      \n

      swap(&123, &456);
      No, you cannot do this because &123 and &456 are not valid addresses in memory. 123 and 456 are constants, not variables, so you cannot take their addresses for swapping the content.

    • \n
  • \n
  • What does the following code print:

    \n
    int func() {
    int array[] = { 4, 2, 9, 3, 8 };
    int *P = NULL;
    int i = 0;

    p = &array[2];
    p++;
    printf("%d\\n", *(p++));
    *(--p) = 7;

    (*p)++;
    for (i = 0; i < (sizeof(array)/sizeof(int)); i++) {
    printf("%d ", array[i]);
    }
    }
    \n

    The output is

    \n
    3
    4 2 9 8 8
    \n

    Explanation:

    \n
      \n
    • Initially, p points to array[2] which is 9.
    • \n
    • After p++, p points to array[3] which is 3. The value 3 is printed.
    • \n
    • Then, *(--p) = 7; sets array[3] to 7.
    • \n
    • Next, (*p)++; increments the value at array[3] (which is now 7) to 8.
    • \n
    • Finally, the for loop prints the elements of the array, which are 4 2 9 8 8.
    • \n
  • \n
  • Write a subroutine called clear_it that accepts a pointer to integer and an integer that indicates the size of the space that the pointer points to. clear_it should set all of the elements that the pointer points to to zero.

    \n

    void clear_it(int *ptr, int size) {
    for (int i = 0; i < size; i++) {
    *(ptr + i) = 0;
    }
    }

  • \n
  • Write a subroutine called add_vectors that accepts three pointers to integer and a fourth parameter to indicate the size of the spaces that the pointers point to. add_vectors should add the elements of the first two 'vectors' together and store them in the third 'vector'. e.g. if two arrays of 10 integers, A and B, were to be added together and the result stored in an array C of the same size, the call would look like add_vectors(a, b, c, 10); and, as a result, c[5] would be the sum of a[5] and b[5]

    \n

    All four implementations below are equivalent solutions to this problem:

    \n

    void add_vectors(int *a, int *b, int *c, int size) {
    for (int i = 0; i < size; i++) {
    c[i] = a[i] + b[i];
    }
    }

    void add_vectors1(int *a, int *b, int *c, int size) {
    int *end = c + size;
    while (c < end) {
    *c++ = *a++ + *b++;
    }
    }

    void add_vectors2(int *a, int *b, int *c, int size) {
    for (int i=0; i<size; i++) {
    *c++ = *a++ + *b++;
    }
    }

    void add_vectors3(int *a, int *b, int *c, int size) {
    for (int i=0; i<size; i++) {
    *(c+i) = *(a+i) + *(b+i);
    }
    }

  • \n
\n","categories":["Study Notes"],"tags":["C/C++ Programming"]},{"title":"Programming in C Exam Review and Practices (II)","url":"/en/2024/03/26/C-Prog-Exam-Review-Practices-2/","content":"

Here is a series of general study guides to college-level C programming courses. This is the second part covering dynamic memory allocation, advanced pointer operations, recursion, linked list and tree common functions, etc.

\n

Dynamic Memory Allocation

\n
    \n
  • Given the following definitions:

    \n

    int *pi = NULL;
    float *pf = NULL;
    char *pc = NULL;
    char my_string[] = "Hello, World!";

    \n

    write statements to do the following memory operations:

    \n
      \n
    • reserve space for 100 integers and assign a pointer to that space to pi

      \n

      pi = (int *)malloc(sizeof(int) * 100);
      assert(pi != NULL);

    • \n
    • reserve space for 5 floats and assign a pointer to that space to pf

      \n

      pf = (float *)malloc(sizeof(float) * 5);
      assert(pf != NULL);

    • \n
    • unreserve the space that pi points to

      \n

      free(pi);
      pi = NULL;

    • \n
    • reserve space for enough characters to hold the string in my_string and assign a pointer to that space to pc. Copy my_string into that space.

      \n

      pc = (char *)malloc(strlen(my_string) + 1));
      assert(pc != NULL);
      strcpy(pc, mystring);

    • \n
    • free everything that hasn't been unreserved yet.

      \n

      free(pc);
      free(pf);
      pc = NULL;
      pf = NULL;

    • \n
  • \n
  • What happens if you reserve memory and assign it to a pointer named p and then reserve more memory and assign the new pointer to p? How can you refer to the first memory reservation?
    \nIf you reserve then assign then reserve more memory you will have a memory leak. If you want to refer to the first pointer, you can set a new pointer to point to the new one before reserving more memory.

  • \n
  • Does it make sense to free() something twice? What's a good way to prevent this from happening?
    \nNo, it doesn’t make sense to free something twice, a good way to prevent this is setting the thing you freed to NULL after freeing it.

  • \n
\n

Advanced Pointer Operations

\n
    \n
  • Suppose p is a pointer to a structure and f is one of its fields. What is a simpler way of saying: x = (*p).f;.

    \n

    x = p->f;

  • \n
  • Given the following declarations and definitions:

    \n

    struct s {
    \tint x;
    \tstruct s *next;
    };
    what will the following code print?

    \n

    struct s *p1 = NULL;
    struct s *p2 = NULL;
    struct s *p3 = NULL;
    struct s *p4 = NULL;
    struct s *p5 = NULL;
    p5 = malloc(sizeof(struct s));
    p5->x = 5;
    p5->next = NULL;
    p4 = malloc(sizeof(struct s));
    p4->x = 4;
    p4->next = p5;
    p3 = malloc(sizeof(struct s));
    p3->x = 3;
    p3->next = p4;
    p2 = malloc(sizeof(struct s));
    p2->x = 2;
    p2->next = p3;
    p1 = malloc(sizeof(struct s));
    p1->x = 1;
    p1->next = p2;
    printf("%d %d\\n", p1->next->next->next->x, p2->next->x);

    \n

    It will print \"4 3\".

  • \n
  • Write a subroutine called do_allocate that is passed a pointer to the head pointer to a list of block structures: do_allocate(struct block **). If the head pointer is NULL, do_allocate should allocate a new struct block and make the head pointer point to it. If the head is not NULL, the new struct block should be prepended to the list, and the head pointer set to point to it.

    \n

    This is a linked list insertion function. New data items should always be inserted into the front of the list. Note the input argument has to be a pointer to pointer to make a change to the original head pointer. A sample solution is shown below

    \n

    #include <stdlib.h>

    struct block {
    int data;
    struct block *next;
    };

    void do_allocate(struct block **head) {
    struct block *new_block = malloc(sizeof(struct block));
    if (new_block == NULL) {
    // Handle memory allocation failure
    return;
    }

    // Initialize the new block
    new_block->data = 0;
    new_block->next = *head;

    // Update the head pointer
    *head = new_block;
    }

  • \n
  • Write a subroutine called my_free that will accept a pointer to a pointer of some arbitrary type and:

    \n
      \n
    • free the space pointed to by the pointer
    • \n
    • set the pointer to NULL
    • \n
    \n

    #include <stdlib.h>

    void my_free(void **ptr) {
    if (ptr != NULL && *ptr != NULL) {
    free(*ptr);
    *ptr = NULL;
    }
    }

  • \n
  • Given the following declaration:

    \n

    struct employee {
    char *name;
    char *title;
    int id;
    };
    write a subroutine called create_employee that accepts two string parameters for the new name and title and one integer parameter for the ID. It should return a newly allocated Employee structure with all of the fields filled in.

    \n

    #include <stdlib.h>
    #include <string.h>

    struct employee *create_employee(const char *name, const char *title, int id)
    {
    struct employee *new_employee = malloc(sizeof(struct employee));
    if (new_employee == NULL) {
    return NULL;
    }

    // Allocate memory for the name and copy the string
    new_employee->name = malloc(strlen(name) + 1);
    if (new_employee->name == NULL) {
    free(new_employee);
    return NULL;
    }
    strcpy(new_employee->name, name);

    // Allocate memory for the title and copy the string
    new_employee->title = malloc(strlen(title) + 1);
    if (new_employee->title == NULL) {
    free(new_employee->name);
    free(new_employee);
    return NULL;
    }
    strcpy(new_employee->title, title);

    // Set the ID
    new_employee->id = id;

    return new_employee;
    }

  • \n
  • Write a subroutine called fire_employee that accepts a pointer to pointer to struct employee, frees its storage and sets the pointer that points to the storage to NULL.

    \n

    void fire_employee(struct employee **emp_ptr) {
    if (emp_ptr != NULL && *emp_ptr != NULL) {
    \tfree((*emp_ptr)->name);
    \tfree((*emp_ptr)->title);
    \tfree(*emp_ptr);
    \t*emp_ptr = NULL;
    }
    }

  • \n
\n

Recursion

\n
    \n
  • Create a recursive function to compute the factorial function.

    \n

    unsigned long long factorial(unsigned int n) {
    if (n == 0) return 1;
    return n * factorial(n - 1);
    }

  • \n
  • Create a recursive function to compute the Nth element of the Fibonacci sequence: 0 1 1 2 3 5 8 13 21 34 55 ...

    \n

    unsigned int fibonacci(unsigned int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
    }

  • \n
  • Implement a recursive list search. e.g. each function call should either return the list node that it's looking at because it matches the search item or it should return the value from calling itself on the next item in the list.

    \n

    struct Node {
    int data;
    struct Node* next;
    };

    struct Node* search(struct Node* node, int value) {
    if (node == NULL) return NULL;

    if (node->data == value) {
    return node;
    } else {
    // Recursive call on the next node
    return search(node->next, value);
    }
    }

  • \n
\n

Linked List Functions

\n
#include <stdio.h>
#include <stdlib.h>

struct Node {
int data;
struct Node* next;
};


// Assume the list is ordered with decreasing date values,
// insert before all nodes with less or equal data values.
// [7, 5, 5, (new:4), 4, 2, 1]

void insertBefore(struct Node** head, struct Node* newNode) {
if (*head == NULL) {
// If the head is NULL, insert the new node as the first node
newNode->next = NULL;
*head = newNode;
return;
}

// The first node's value is less than or equal to the new node's,
// insert the new node as the new first node.
if ((*head)->data <= newNode->data) {
newNode->next = *head;
*head = newNode;
return;
}

struct Node* current = *head;
while (current->next != NULL && current->next->data > newNode->data) {
current = current->next;
}

newNode->next = current->next;
current->next = newNode;
}

// Assume the list is ordered with decreasing date values,
// insert after all nodes with greater or equal data values.
// [7, 5, 5, 4, (new:4), 2, 1]

void insertAfter(struct Node** head, struct Node* newNode) {
if (*head == NULL) {
// If the head is NULL, insert the new node as the first node
newNode->next = NULL;
*head = newNode;
return;
}

// The first node's value is less than the new node's,
// insert the new node as the new first node.
if ((*head)->data < newNode->data) {
newNode->next = *head;
*head = newNode;
return;
}

struct Node* current = *head;
while (current->next != NULL && current->next->data >= value) {
current = current->next;
}

newNode->next = current->next;
current->next = newNode;
}

void insertAtBeginning(struct Node** head, struct Node* newNode) {
newNode->next = *head;
*head = newNode;
}

void insertAtTail(struct Node** head, struct Node* newNode) {
if (*head == NULL) {
*head = newNode;
return;
}

struct Node* current = *head;
while (current->next != NULL) {
current = current->next;
}

current->next = newNode;
newNode->next = NULL;
}

void printList(struct Node* node) {
while (node != NULL) {
printf("%d ", node->data);
node = node->next;
}
printf("\\n");
}

int main() {
struct Node* head = NULL;
struct Node* node1 = (struct Node*)malloc(sizeof(struct Node));
node1->data = 1;
node1->next = NULL;

struct Node* node2 = (struct Node*)malloc(sizeof(struct Node));
node2->data = 3;
node2->next = NULL;

struct Node* node3 = (struct Node*)malloc(sizeof(struct Node));
node3->data = 5;
node3->next = NULL;

insertAtBeginning(&head, node1);
insertAfter(&head, node2);
insertBefore(&head, node3, 4);
insertAtTail(&head, node3);

printf("Linked list after insertion: ");
printList(head);

return 0;
}
\n

Tree Common Functions

\n
#include <stdio.h>
#include <stdlib.h>

struct Node {
int data;
struct Node* left;
struct Node* right;
};

struct Node* createNode(int value) {
struct Node* newNode = malloc(sizeof(struct Node));
newNode->data = value;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

struct Node* insertNode(struct Node* root, int value) {
if (root == NULL) {
return createNode(value);
}

if (value < root->data) {
root->left = insertNode(root->left, value);
} else if (value > root->data) {
root->right = insertNode(root->right, value);
}

return root;
}

struct Node* minValueNode(struct Node* node) {
struct Node* current = node;
while (current && current->left != NULL) {
current = current->left;
}
return current;
}

struct Node* maxValueNode(struct Node* node) {
struct Node* current = node;
while (current && current->right != NULL) {
current = current->right;
}
return current;
}

void inorderTraversal(struct Node* root) {
if (root != NULL) {
inorderTraversal(root->left);
printf("%d ", root->data);
inorderTraversal(root->right);
}
}

int main() {
struct Node* root = NULL;
root = insertNode(root, 50);
insertNode(root, 30);
insertNode(root, 20);
insertNode(root, 40);
insertNode(root, 70);
insertNode(root, 60);

printf("Inorder traversal: ");
inorderTraversal(root);
printf("\\n");

return 0;
}
\n

Local, Static and Global Variables

\n
    \n
  • Try the following two programs to appreciate the differences between static and non-static local variables.

    \n

    void try() {                    void try() {
    int x = 0; static int x = 0;
    if (x == 0) { if (x == 0) {
    x = 5; x = 5;
    } }
    x++; x++;
    printf("X = %d\\n", x); printf("X = %d\\n", x);
    } }
    int main() { int main() {
    int i=0; int i=0;
    for (i=0; i<10; i++) for (i=0; i<10; i++)
    try(); try();
    } }
    // Output "X = 6" always // Output "X = 6/7/8/..."

  • \n
  • What happens if you define a global variable with a static storage class in one module and attempt to refer to that variable in a different module?
    \nThe variable will not be accessible in the other module. This is because static variables have internal linkage by default, meaning they are only accessible within the same module.

  • \n
  • Can a function be declared with a static storage class? If so, how? If not, why not?
    \nYes, you can declare a function with the static storage class, you can use the static keyword. It means that the function has internal linkage, which restricts its scope to the current translation unit (i.e., the source file in which it is defined). This means that the function can only be called from within the same source file, and its name is not visible outside of that file.

  • \n
  • Create a global variable in one module and, in another module use an \"extern\" declaration to refer to it.

    \n

    module1.c
    int globalVariable = 42;

    \n

    module2.c
    extern int globalVariable; // Declare the global variable from module1

    int main() {
    printf("The value of globalVariable is: %d\\n", globalVariable);
    return 0;
    }

  • \n
\n

Types

\n
    \n
  • Under what conditions can you qualify a type as \"const\"?
    \nThe const keyword is used to indicate that the value of the object with that type cannot be modified.

  • \n
  • What is the difference between the following types?

    \n

    const char * cp1;
    char * const cp2;
    const char * const cp3;

    \n

    const char * cp1;: This declares cp1 as a pointer to a constant char. It means that the data cp1 points to cannot be modified through cp1, but cp1 itself can be changed to point to a different memory location.

    \n

    char * const cp2;: This declares cp2 as a constant pointer to a char. It means that cp2 always points to the same memory location, and this memory location cannot be changed. However, the data at this memory location can be modified through cp2.

    \n

    const char * const cp3;: This declares cp3 as a constant pointer to a constant char. It means that both cp3 and the data it points to are constant. cp3 cannot be changed to point to a different memory location, and the data it points to cannot be modified through cp3.

    \n

    In summary:

    \n
      \n
    • const to the left of * makes the data constant.
    • \n
    • const to the right of * makes the pointer constant.
    • \n
    • const on both sides makes both the pointer and the data constant.
    • \n
  • \n
  • Name all of the first-class types in \"C\".
    \nScalar types (e.g., int, float, double, char, void, short, long, etc.)

  • \n
  • Give an example of a derived type in \"C\".
    \nPointer types (e.g., int *, char *, etc.).
    \nPointer to function types (e.g., int (*)(int, int), a pointer to a function that takes two int arguments and returns an int)

    \n

    An example is declaring a struct type, e.g.:

    \n

    struct person {
    \tchar name[20];
    \tint age;
    \tfloat height;
    };

  • \n
  • Can you assign a float variable to an int variable?
    \nYes, but the value will be truncated.

  • \n
  • Can you assign an int variable to a float variable?
    \nYes, but the type will be promoted.

  • \n
  • Can you assign any first-class type variable to any other first-class type variable?
    \nYes, you just have to typecast them to the matching data type.

  • \n
  • Can you assign a first-class type variable to any kind of derived type variable?
    \nNo, e.g. you cannot assign an int to a structure

  • \n
\n

C Preprocessor and Libraries

\n
    \n
  • Review how to use the following preprocessor directives:
  • \n
\n
#define SOMETHING SOMETHING_ELSE
...
#ifdef SOMETHING
...
#else
...
#endif
\n

#define is a preprocessor directive in C that unconditionally defines a macro.

\n

#ifdef is a preprocessor directive in the C programming language that tests whether a macro has been defined or not. It allows conditional compilation of code based on whether a particular macro has been defined or not.

\n

#else is run if the macro is not defined in a #ifdef

\n

#endif Ends a #ifdef macro

\n
#if (SOMETHING == 5)
\n

#if is a preprocessor directive in the C programming language that allows conditional compilation of code based on the value of an expression.

\n
    \n
  • Does the following program cause a compile-time error?

    \n

    #define C 1
    #define A B
    #define B C
    int function() {
    \tint x = 0;
    #if (A == 1)
    \treturn;
    #else
    \treturn 0;
    #endif
    }
    No, no compile time error. The macro A is defined as B and B is defined as C. So when the preprocessor replaces A in the #if directive, it replaces it with B and then replaces B with C. Therefore, the #if statement is effectively replaced by #if (C == 1).

    \n

    Since C is defined as 1, the condition in the #if statement evaluates to true, and the code in the first branch of the if statement is executed, which is a return statement without a value.

    \n

    In this specific case, the program still works because the function return type is int, and the return statement in the first branch of the if statement might just return some undetermined number.

    \n

    In general, however, it is good practice to always explicitly return a value from a function that has a return type, as it makes the code more clear and less error-prone.

  • \n
  • What are the reasons for using libraries?
    \nTo import useful code, promote modular programming, and provide cross-platform compatibility.

  • \n
  • What are the differences between static and dynamic (shared) libraries?

    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    AspectsStatic libraryDynamic library
    LinkingLinked at compile timeLinked at run time
    SizeIncrease the size of the executable (the library code is included in the executable.Reduce the size of the executable (the library code is stored separately and referenced at run time)
    Memory UsageIncrease memory usage (the entire library code is loaded into memory)Reduce memory usage (the code is shared among multiple processes, and only one copy of the library code is loaded into memory)
    Ease of UpdatesRequire recompilation of the entire programAllow for easier updates (can replace the library file without recompiling the program)
    PortabilityMore portable (does not require the presence of the library file at run time)Less portable (requires the library file to be present and correctly configured at run time)
    Runtime DependenciesNo (directly included in the executable)Yes (must be present in the correct location for the program to run)
  • \n
  • What are the trade-offs between the above two?
    \nThe trade-offs between static and dynamic libraries involve executable size, memory usage, ease of updates, runtime dependencies, portability, and performance considerations.

  • \n
  • How do you create a library?
    \nCompile c files into an object file and link them with

    \n

    gcc (name).o –shared –o library.so

  • \n
\n","categories":["Study Notes"],"tags":["C/C++ Programming"]},{"title":"The Inductive Proof and Applications of Fermat's Little Theorem","url":"/en/2023/11/14/Fermats-Little-Theorem/","content":"

In the history of mathematics, Pierre de Fermat was a special figure. His formal occupation was as a lawyer, but he was exceptionally fond of mathematics. Although an amateur, Fermat’s achievements in mathematics were no less than those of professional mathematicians of the same era. He contributed to modern calculus, analytic geometry, probability, and number theory. Especially in the field of number theory, Fermat was most interested and achieved the most outstanding results.

\n

Logic is the foundation of the certainty of all the knowledge we acquire.
Leonhard Euler (Swiss mathematician, physicist, astronomer, geographer, logician, and engineer, one of the greatest mathematicians in history)

\n
\n

As the \"king of amateur mathematicians\", Fermat proposed some famous conjectures in number theory but did not give strong proof. The most famous is Fermat's Last Theorem1. Although Fermat claimed he had found an ingenious proof, there was not enough space on the margin to write it down. But in fact, after more than 350 years of unremitting efforts by mathematicians, it was not until 1995 that British mathematician Andrew John Wiles and his student Richard Taylor published a widely recognized proof.

\n
\n\"Ferma
Ferma and Fermat's Last Theorem On Stamp
\n
\n

In contrast, there is also a little theorem of Fermat. In October 1640, Fermat first wrote down words equivalent to the following in a letter to a friend:

\n
\n

If \\(p\\) is a prime and \\(a\\) is any integer not divisible by \\(p\\), then \\(a^{p-1}-1\\) is divisible by \\(p\\).

\n
\n

Similarly, Fermat did not give proof in the letter. Nearly a hundred years later, the complete proof was first published by the great mathematician Euler in 1736. Later, people found in the unpublished manuscripts of another great mathematician Leibniz that he had obtained almost the same proof before 1683.

\n

Fermat's little theorem is one of the fundamental results of elementary number theory. This theorem can be used to generate primality testing rules and corresponding verification algorithms. In the late 1970s, public key cryptography emerged, and Fermat's little theorem helped prove the correctness of RSA. Afterward, researchers combined it with the Chinese remainder theorem and also discovered an optimized method for RSA decryption and signing. The following further introduces these applications.

\n

Theorem and Corollaries

\n

The complete statement of Fermat's little theorem is: If \\(\\pmb{p}\\) is a prime number, then for any integer \\(\\pmb{a}\\), the number \\(\\pmb{a^p−a}\\) is an integer multiple of \\(\\pmb{p}\\). In the notation of modular arithmetic, this is expressed as \\(\\pmb{a^p\\equiv a\\pmod p}\\). If \\(\\pmb{a}\\) is not divisible by \\(\\pmb{p}\\), then \\(\\pmb{a^{p-1}\\equiv 1\\pmod p}\\).

\n

From \\(a^{p-1}\\equiv 1\\pmod p\\) it can be deduced that \\(\\pmb{a^{p-2}\\equiv a^{-1}\\pmod p}\\). This new congruence just gives a way to find the multiplicative inverse of \\(a\\) modulo \\(p\\). This is a direct corollary of Fermat's little theorem.

\n

Another important corollary is: If \\(\\pmb{a}\\) is not a multiple of \\(\\pmb{p}\\) and \\(\\pmb{n=m\\bmod {(p-1)}}\\), then \\(\\pmb{a^n\\equiv a^m\\pmod p}\\). This inference does not seem very intuitive, but the proof is simple:

\n
    \n
  1. Because \\(n=m\\bmod {(p-1)}\\), it follows that \\(m = k⋅(p-1)+n\\)
  2. \n
  3. Substituting the result into the power operation, \\(a^m=a^{k⋅(p-1)+n}=(a^{(p-1)})^k⋅a^n\\)
  4. \n
  5. Then applying modular arithmetic and Fermat's little theorem, \\(a^m=(a^{(p-1)})^k⋅a^n\\equiv (1)^ka^n\\equiv a^n\\pmod p\\)
  6. \n
  7. Therefore \\(a^n\\equiv a^m\\pmod p\\), Q.E.D.
  8. \n
\n

Proof by Induction

\n

There are many ways to prove Fermat's little theorem. Among them, mathematical induction based on the binomial theorem is the most intuitive one. First, for \\(a=1\\), it is obvious that \\(1^p \\equiv 1\\pmod{p}\\) holds. Now assume that for an integer \\(a\\), \\(a^p \\equiv a \\pmod{p}\\) is true. As long as it is proved under this condition that \\((a+1)^p\\equiv a+1\\pmod{p}\\), the proposition holds.

\n

According to the binomial theorem, \\[(a+1)^p = a^p + {p \\choose 1} a^{p-1} + {p \\choose 2} a^{p-2} + \\cdots + {p \\choose p-1} a + 1\\] Here the binomial coefficient is defined as \\({p \\choose k}= \\frac{p!}{k! (p-k)!}\\). Note that because \\(p\\) is a prime number, for \\(1≤k≤p-1\\), each binomial coefficient \\({p \\choose k}\\)is a multiple of \\(p\\).

\n

Then taking \\(\\bmod p\\), all the intermediate terms disappear, leaving only \\(a^p+1\\) \\[(a+1)^p \\equiv a^p + 1 \\pmod{p}\\]Referring to the previous assumption \\(a^p ≡ a \\pmod p\\), it infers that \\((a+1)^p \\equiv a+1 \\pmod{p}\\), the proof is complete.

\n

Applications of the Theorem

\n

Solution to Math Competition Problems

\n

Fermat's little theorem provides concise solutions to some seemingly complicated computational problems. First look at a simple example: If today is Sunday, what day will it be in \\(2^{100}\\) days? There are 7 days in a week. According to Fermat's little theorem, we have \\(2^{7−1}≡1\\bmod 7\\), from which we can get \\[2^{100}=2^{16×6+4} ≡ 1^{16}×2^4≡16≡2\\pmod 7\\]So the answer is Tuesday. This actually repeats the proof process of the second corollary above with specific numbers. Applying this corollary can greatly speed up modular exponentiation. For example, to calculate \\(49^{901}\\bmod 151\\), since \\(901\\bmod(151-1)=1\\), it can be deduced immediately that \\[49^{901}\\equiv 49^1\\equiv 49\\pmod {151}\\]

\n

Now look at a question that seems a little more difficult: Given the equation \\(133^5+110^5+84^5+27^5=n^{5}\\), find the value of \\(n\\).

\n

At first glance, there seems to be no clue, so start with basic parity checking. The left side of the equation has two odd terms and two even terms, so the total is even, which also determines that \\(n\\) must be even. Looking at the exponent 5 which is a prime number, and thinking of Fermat's little theorem, we get \\(n^5≡n\\pmod 5\\), therefore \\[133^5+110^5+84^5+27^5≡n\\pmod 5\\] \\[3+0+4+2≡4≡n\\pmod 5\\] Continuing to take modulo 3, according to the corollary of Fermat's little theorem again, we have \\(n^5≡n^{5\\mod(3-1)}≡n\\pmod 3\\). So \\[133^5+110^5+84^5+27^5≡n\\pmod 3\\] \\[1+2+0+0≡0≡n\\pmod 3\\]

\n

Okay, now summarize:

\n
    \n
  1. \\(n\\) should be greater than 27 and an even number
  2. \n
  3. \\(n\\) is a multiple of 3, so the sum of all digits is a multiple of 3
  4. \n
  5. \\(n\\) divided by 5 gives a remainder of 4, the ones place should be 4 (9 does not satisfy the condition of an even number)
  6. \n
\n

These lead to \\(n = 144\\) or \\(n\\geq 174\\). Obviously, 174 is too big. It can be concluded that n can only be 144.

\n

This question actually appeared in the 1989 American Invitational Mathematics Examination (AIME), which is a math competition for high school students. Interestingly, the solution to the question happens to disprove Euler's conjecture.

\n

Primality Testing

\n

Many encryption algorithm applications require \"random\" large prime numbers. The common method to generate large primes is to randomly generate an integer and then test for primality. Since Fermat’s little theorem holds on the premise that p is a prime number, this provides a prime test method called the Fermat primality test. The test algorithm is

\n
\n

Input: \\(n\\) - the number to be tested, \\(n>3\\); \\(k\\) - the number of iterations
\nOutput: \\(n\\) is composite, otherwise may be prime
\nRepeat k times:
\n\\(\\quad\\quad\\)Randomly select an integer \\(a\\) between \\([2, n-2]\\)
\n\\(\\quad\\quad\\)If \\(a^{n-1}\\not \\equiv 1{\\pmod n}\\), return \\(n\\) is composite
\nReturn \\(n\\) may be prime

\n
\n

It can be seen that Fermat’s primality test is non-deterministic. It uses a probabilistic algorithm to determine whether a number is composite or probably prime. When the output is composite, the result is definitely correct; but those numbers tested to be probably prime may actually be composite, such numbers are called Fermat pseudoprimes. The smallest Fermat pseudoprime is 341, with \\(2^{340}\\equiv1\\pmod {341}\\) but \\(341=11×31\\). So in fact, Fermat's little theorem provides a necessary but insufficient condition for determining prime numbers. It can only be said that the more iterations performed, the higher the probability that the tested number is prime.

\n

There is also a class of Fermat pseudoprimes \\(n\\) which are composite numbers themselves, but for any integer \\(x\\) that is coprime with \\(n\\), it holds \\(x^{n-1}\\equiv 1\\pmod n\\). In number theory, they are called Carmichael numbers. The smallest Carmichael number is 561, equal to \\(3×11×17\\). Carmichael numbers can fool Fermat’s primality test, making the test unreliable. Fortunately, such numbers are very rare. Statistics show that among the first \\(10^{12}\\) natural numbers there are only 8241 Carmichael numbers.

\n

The PGP encryption communication program uses Fermat’s primality test in its algorithm. In network communication applications requiring large primes, Fermat’s primality test method is often used for pretesting, followed by calling the more efficient Miller-Rabin primality test to ensure high accuracy.

\n

Proof of RSA Correctness

\n

Fermat's little theorem can also be used to prove the correctness of the RSA algorithm, that is, the decryption formula can completely restore the plaintext \\(m\\) from the ciphertext \\(c\\) without errors: \\[c^d=(m^{e})^{d}\\equiv m\\pmod {pq}\\]

\n

Here \\(p\\) and \\(q\\) are different prime numbers, \\(e\\) and \\(d\\) are positive integers that satisfy \\(ed≡1\\pmod {λ(pq)}\\), where \\(λ(pq)=\\mathrm{lcm}(p−1,q−1)\\). \\(\\mathrm{lcm}\\) is the least common multiple function.

\n

Before starting the proof, first introduce a corollary of the Chinese remainder theorem: If integers \\(\\pmb{n_1,n_2,...,n_k}\\) are pairwise coprime and \\(\\pmb{n=n_{1}n_{2}...n_{k}}\\), then for any integer \\(\\pmb x\\) and \\(\\pmb y\\), \\(\\pmb{x≡y\\pmod n}\\) holds if and only if \\(\\pmb{x≡y\\pmod{n_i}}\\) for each \\(\\pmb{i=1,2,...k}\\). This corollary is easy to prove, details are left as an exercise2. According to this corollary, if \\(m^{ed}≡m\\pmod p\\) and \\(m^{ed}≡m\\pmod q\\) are both true, then \\(m^{ed}≡m\\pmod{pq}\\) must also hold.

\n

Now look at the first step of the proof. From the relationship between \\(e\\) and \\(d\\), it follows \\(ed-1\\) can be divided by both \\(p-1\\) and \\(q-1\\), that is, there exist non-negative integers \\(h\\) and \\(k\\) satisfying: \\[ed-1=h(p-1)=k(q-1)\\]

\n

The second step is to prove \\(m^{ed}≡m\\pmod p\\). Consider two cases:

\n
    \n
  1. If \\(m≡ 0\\pmod p\\), i.e. \\(m\\) is an integer multiple of \\(p\\), then naturally \\(m^{ed}≡0≡m\\pmod p\\)
  2. \n
  3. If \\(m\\not \\equiv 0\\pmod p\\), it can be deduced that: \\[m^{ed}=m^{ed-1}m=m^{h(p-1)}m=(m^{p-1})^{h}m\\equiv 1^{h}m\\equiv m{\\pmod {p}}\\]Here Fermat’s little theorem \\(m^{p−1}≡1\\pmod p\\) is applied.
  4. \n
\n

The third step has the goal of proving \\(m^{ed}≡m\\pmod q\\). The deduction process is similar to the previous step, and it can also be deduced that m^ed ≡ m (mod q):

\n
    \n
  1. If \\(m≡ 0\\pmod p\\), i.e. \\(m\\) is an integer multiple of \\(q\\), then naturally \\(m^{ed}≡0≡m\\pmod q\\)
  2. \n
  3. If \\(m\\not \\equiv 0\\pmod q\\), it can be deduced that: \\[m^{ed}=m^{ed-1}m=m^{h(q-1)}m=(m^{q-1})^{h}m\\equiv 1^{h}m\\equiv m{\\pmod {q}}\\]
  4. \n
\n

Since both \\(m^{ed}≡m\\pmod p\\) and \\(m^{ed}≡m\\pmod q\\) have been proved, \\(m^{ed}≡m\\pmod{pq}\\) holds, Q.E.D.

\n

Optimized RSA Decryption

\n

Combining Fermat’s little theorem and the Chinese remainder theorem can not only verify the correctness of the RSA but also deduce an optimized decryption method.

\n

In the RSA encryption algorithm, the modulus \\(N\\) is the product of two prime numbers \\(p\\) and \\(q\\). Therefore, for any number \\(m\\) less than \\(N\\), letting \\(m_1=m\\bmod p\\) and \\(m_2=m\\bmod q\\), \\(m\\) is uniquely determined by \\((m_1,m_2)\\). According to the Chinese remainder theorem, we can use the general solution formula to deduce \\(m\\) from \\((m_1,m_2)\\). Since \\(p\\) and \\(q\\) each have only half the number of bits as \\(N\\), modular arithmetic will be more efficient than directly computing \\(c^d\\equiv m\\pmod N\\). And in the process of calculating \\((m_1,m_2)\\), applying the corollary of Fermat's little theorem yields: \\[\\begin{align}\nm_1&=m\\bmod p=(c^d\\bmod N)\\bmod p\\\\\n&=c^d\\bmod p=c^{d\\mod(p-1)}\\bmod p\\tag{1}\\label{eq1}\\\\\nm_2&=m\\bmod q=(c^d\\bmod N)\\bmod q\\\\\n&=c^d\\bmod q=c^{d\\mod(q-1)}\\bmod q\\tag{2}\\label{eq2}\\\\\n\\end{align}\\]

\n

Obviously, in above \\((1)\\) and \\((2)\\) the exponent \\(d\\) is reduced to \\(d_P=d\\bmod (p-1)\\) and \\(d_Q=d\\bmod (q-1)\\) respectively, which further speeds up the calculation. Finally, the step of calculating \\(m\\) is further optimized using the Garner algorithm3: \\[\\begin{align}\nq_{\\text{inv}}&=q^{-1}\\pmod {p}\\\\\nh&=q_{\\text{inv}}(m_{1}-m_{2})\\pmod {p}\\\\\nm&=m_{2}+hq\\pmod {pq}\\tag{3}\\label{eq3}\n\\end{align}\\] Note that given \\((p,q,d)\\), the values of \\((d_P,d_Q,q_\\text{inv})\\) are determined. So they can be precomputed and stored. For decryption, only \\((m_1,m_2,h)\\) are to be calculated and substituted into the above (3).

\n

This is actually the decryption algorithm specified in the RSA cryptography standard RFC 8017 (PKCS #1 v2.2). The ASN.1 formatted key data sequence described by this specification corresponds exactly to the above description (\\(d_P\\) - exponent1,\\(d_Q\\) - exponent2,\\(q_{\\text{inv}}\\) - coefficient):

\n
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
\n

The widely used open-source library OpenSSL implements this efficient and practical decryption algorithm. As shown below, the key data generated using the OpenSSL command line tool is consistent with the PKCS #1 standard:

\n
# Generate 512-bit RSA keys saved in PEM format file.
# For demo only, DON'T USE 512-bit KEYS IN PRODUCTION!
$ openssl genrsa -out private-key.pem 512
Generating RSA private key, 512 bit long modulus
.++++++++++++
......................++++++++++++
e is 65537 (0x10001)

# Inspect RSA keys saved in a PEM format file.
$ openssl pkey -in private-key.pem -text
-----BEGIN PRIVATE KEY-----
MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEA7HwgswSjqvDRPWj3
vVIxMZDAtXJCa7Qx+2jFv7e7GXB8+fa3MTBL36YjIcAgLeCHAyIzWkPndxvTJE2l
WvYzRQIDAQABAkBCUp2pF0f/jQJhwqqYQhDh4cLqIF1Yb3UFGWE8X37tpwCifAqg
t8NEpaXWkct5M+YxqjKfdOKYy0TVcJRlyS+RAiEA9xujHmh+bOvl0xWDFoARDAHw
v94qRCpeRNveHFpNvPsCIQD0/qFpeSjRWj/4vjCkIOv1RbbhDHVsgsF9HRJNW2Rc
vwIgaGIAUcQKQ7CScMxRh5upl8zqCeKrMAhFsgi+lnN/CykCIDMdAL4Jmht7ccdK
nslPWQs1/T6co878xLN+ojfjbl/vAiEAhmp4YDX1g8kFh6cVtTIDT5AGtzqwB2Jw
cCq+IoKDYBc=
-----END PRIVATE KEY-----
Private-Key: (512 bit)
modulus:
00:ec:7c:20:b3:04:a3:aa:f0:d1:3d:68:f7:bd:52:
31:31:90:c0:b5:72:42:6b:b4:31:fb:68:c5:bf:b7:
bb:19:70:7c:f9:f6:b7:31:30:4b:df:a6:23:21:c0:
20:2d:e0:87:03:22:33:5a:43:e7:77:1b:d3:24:4d:
a5:5a:f6:33:45
publicExponent: 65537 (0x10001)
privateExponent:
42:52:9d:a9:17:47:ff:8d:02:61:c2:aa:98:42:10:
e1:e1:c2:ea:20:5d:58:6f:75:05:19:61:3c:5f:7e:
ed:a7:00:a2:7c:0a:a0:b7:c3:44:a5:a5:d6:91:cb:
79:33:e6:31:aa:32:9f:74:e2:98:cb:44:d5:70:94:
65:c9:2f:91
prime1:
00:f7:1b:a3:1e:68:7e:6c:eb:e5:d3:15:83:16:80:
11:0c:01:f0:bf:de:2a:44:2a:5e:44:db:de:1c:5a:
4d:bc:fb
prime2:
00:f4:fe:a1:69:79:28:d1:5a:3f:f8:be:30:a4:20:
eb:f5:45:b6:e1:0c:75:6c:82:c1:7d:1d:12:4d:5b:
64:5c:bf
exponent1:
68:62:00:51:c4:0a:43:b0:92:70:cc:51:87:9b:a9:
97:cc:ea:09:e2:ab:30:08:45:b2:08:be:96:73:7f:
0b:29
exponent2:
33:1d:00:be:09:9a:1b:7b:71:c7:4a:9e:c9:4f:59:
0b:35:fd:3e:9c:a3:ce:fc:c4:b3:7e:a2:37:e3:6e:
5f:ef
coefficient:
00:86:6a:78:60:35:f5:83:c9:05:87:a7:15:b5:32:
03:4f:90:06:b7:3a:b0:07:62:70:70:2a:be:22:82:
83:60:17
\n
\n
\n
    \n
  1. Also known as \"Fermat's conjecture\",its gist is that, when \\(n > 2\\), the equation \\(x^{n}+y^{n}=z^{n}\\) has no positive integer solutions \\((x, y, z)\\). After it was finally proven correct in 1995, it became known as \"Fermat's last theorem.\"↩︎

  2. \n
  3. Hint: If two integers are congruent modulo \\(n\\), then \\(n\\) is a divisor of their difference.↩︎

  4. \n
  5. Garner, H., \"The Residue Number System\", IRE Transactions on Electronic Computers, Volume EC-8, Issue 2, pp.140-147, DOI 10.1109/TEC.1959.5219515, June 1959↩︎

  6. \n
\n
\n","categories":["Study Notes"],"tags":["Cryptography"]},{"title":"IPv4 and IPv6 Header Checksum Algorithm Explained","url":"/en/2021/12/26/IPv4-IPv6-checksum/","content":"

About the IP packet header checksum algorithm, simply put, it is 16-bit ones' complement of the ones' complement sum of all 16-bit words in the header. However, not many sources show exactly how this is done. The same checksum algorithm is used by TCP segment and UDP datagram, but the data involved in the checksum computing is different from that in the IP header. In addition, the checksum operation of the IPv6 packet is different from that of IPv4. Therefore, it is necessary to make a comprehensive analysis of the checksum algorithm of IP packets.

\n

Nothing in life is to be feared, it is only to be understood.
Marie Curie (Polish and naturalized-French physicist and chemist, twice Nobel Prize winner)

\n
\n

IPv4 Header Checksum

\n

IPv4 packet header format can be seen below

\n
0                   1                   2                   3    
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\n

Here the 16-bit Header Checksum field is used for error-checking of the IPv4 header. While computing the IPv4 header checksum, the sender first clears the checksum field to zero, then calculates the sum of each 16-bit value within the header. The sum is saved in a 32-bit value. If the total number of bytes is odd, the last byte is added separately.

\n

After all additions, the higher 16 bits saving the carry is added to the lower 16 bits. Repeat this till all higher 16 bits are zeros. Finally, the sender takes the ones' complement of the lower 16 bits of the result and writes it to the IP header checksum field.

\n

The following demonstrates the entire calculation process using actual captured IPv4 packets.

\n
0x0000: 00 60 47 41 11 c9 00 09 6b 7a 5b 3b 08 00 45 00 
0x0010: 00 1c 74 68 00 00 80 11 59 8f c0 a8 64 01 ab 46
0x0020: 9c e9 0f 3a 04 05 00 08 7f c5 00 00 00 00 00 00
0x0030: 00 00 00 00 00 00 00 00 00 00 00 00
\n

At the beginning of the above 16-bit hex dump is the Ethernet frame header. The IP packet header starts from offset 0x000e, with the first byte 0x45 and the last byte 0xe9. Based on the previous description of the algorithm, we can make the following calculations:

\n
(1) 0x4500 + 0x001c + 0x7468 + 0x0000 + 0x8011 +
0x0000 + 0xc0a8 + 0x6401 + 0xab46 + 0x9ce9 = 0x3a66d
(2) 0xa66d + 0x3 = 0xa670
(3) 0xffff - 0xa670 = 0x598f
\n

Notice at step (1) we replace the checksum field with 0x0000. As can be seen, the calculated header checksum 0x598f is the same as the value in the captured packet. This calculating process is only used for the sender to generate the initial checksum. In practice, for the intermediate forwarding router and the final receiver, they can just sum up all header fields of the received IP packet by the same algorithm. If the result is 0xffff, the checksum verification passes.

\n

C Program Implementation

\n

How to program IPv4 header checksum computing? RFC 1071 (Computing the Internet Checksum) shows a reference \"C\" language implementation:

\n
{
/* Compute Internet Checksum for "count" bytes
* beginning at location "addr".
*/
register long sum = 0;

while( count > 1 ) {
/* This is the inner loop */
sum += * (unsigned short *) addr++;
count -= 2;
}

/* Add left-over byte, if any */
if ( count > 0 )
sum += * (unsigned char *) addr;

/* Fold 32-bit sum to 16 bits */
while (sum>>16)
sum = (sum & 0xffff) + (sum >> 16);

checksum = ~sum;
}
\n

In a real network connection, the source device can call the above code to generate the initial IPv4 header checksum. This checksum is then updated at each step of the routing hop because the router must decrement the Time To Live (TTL) field. RFC 1141 (Incremental Updating of the Internet Checksum) gives a reference implementation of fast checksum update:

\n
unsigned long sum;
ipptr->ttl--; /* decrement ttl */
sum = ipptr->Checksum + 0x100; /* increment checksum high byte*/
ipptr->Checksum = (sum + (sum>>16)); /* add carry */
\n

TCP/UDP Header Checksum

\n

For TCP segment and UDP datagram, both have 16-bit header checksum fields used for error-checking by the destination host. The checksum computing algorithm is the same as the IP header, except for the difference of covered data. Here the checksum is calculated over the whole TCP/UDP header and the payload, plus a pseudo-header that mimics the IPv4 header as shown below:

\n
 0      7 8     15 16    23 24    31 
+--------+--------+--------+--------+
| source address |
+--------+--------+--------+--------+
| destination address |
+--------+--------+--------+--------+
| zero |protocol| TCP/UDP length |
+--------+--------+--------+--------+
\n

It consists of the source and destination IP addresses, the protocol number (TCP:6/UDP:17), and the total length of the TCP/UDP header and payload (in bytes). The purpose of including the pseudo-header in the checksum computing is to confirm the packet reaches the expected destination and avoid IP spoofing attacks. Besides, for IPv4 UDP header checksum is optional, it carries all-zeros if unused.

\n

IPv6 Difference

\n

IPv6 is IP protocol version 6, and its main design goal was to resolve the problem of IPv4 address exhaustion. Of course, it provides many benefits in other aspects. Although IPv6 usage is growing slowly, the trend is unstoppable. The latest IPv6 standard is published in RFC 8200(Internet Protocol, Version 6 (IPv6) Specification).

\n

IPv6 packet header format can be seen below

\n
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| Traffic Class | Flow Label |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Length | Next Header | Hop Limit |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Source Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Destination Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\n

Notice that the IPv6 header does not include a checksum field, a significant difference from IPv4. The absence of a checksum in the IPv6 header furthers the end-to-end principle of Internet design, to simplify router processing and speed up the packet transmission. Protection for data integrity can be accomplished by error detection at the link layer or the higher-layer protocols between endpoints (such as TCP/UDP on the transport layer). This is why IPv6 forces the UDP layer to set the header checksum.

\n

For IPv6 TCP segment and UDP datagram header checksum computing, the pseudo-header that mimics the IPv6 header is shown below

\n
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Source Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Destination Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Upper-Layer Packet Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| zero | Next Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\n

UDP-Lite Application

\n

In actual IPv6 network applications, UDP-Lite (Lightweight UDP) can be used to balance error detection and transmission efficiency. UDP-Lite has its own protocol number 136, and its standard is described in RFC 3828 (The Lightweight User Datagram Protocol (UDP-Lite)).

\n

Referring to the following header format, UDP-Lite uses the same set of port number values assigned by the IANA for use by UDP. But it redefines the Length field in the UDP header to a Checksum Coverage, which allows the application layer to control the length of checksummed data. This is useful for the application that can be tolerant of the potentially lossy transmission of the uncovered portion of the data.

\n
 0              15 16             31
+--------+--------+--------+--------+
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+
| Checksum | |
| Coverage | Checksum |
+--------+--------+--------+--------+
| |
: Payload :
| |
+-----------------------------------+
\n

UDP-Lite protocol defines the values of \"Checksum Coverage\" (in bytes) as shown in the following table:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Checksum CoverageCoverage AreaDescription
0entire UDP-Lites datagramCalculation covers IP pseudo-header
1-7(invalid)The receiver has to drop the datagram
8UDP-Lites headerCalculation covers IP pseudo-header
> 8UDP-Lites header + portion of payload dataCalculation covers IP pseudo-header
> IP datagram length(invalid)The receiver has to drop the datagram
\n

For multimedia applications running VoIP or streaming video data transmission protocols, it'd better receive data with some degree of corruption than not receiving any data at all. Another example is the CAPWAP protocol used to connect Cisco wireless controller and access points. It specifies UDP-Lite as the default transport protocol for the CAPWAP Data channel, while the connection is established over the IPv6 network.

\n

At last, share a C program snippet to present how to initialize a Berkeley socket to establish an IPv6 UDP-Lite connection:

\n
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/udplite.h>

int udplite_conn = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDPLITE);
int val = 8; /* checksum only covers 8-byte UDP-Lite header */
(void)setsockopt(udplite_conn, IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV, &val, sizeof val);
(void)setsockopt(udplite_conn, IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV, &val, sizeof val);
\n

Here IPPROTO_UDPLITE is protocol number 136, which is used together with AF_INET6 address family parameter in socket() function call for IPv6 socket creation. The UDPLITE_SEND_CSCOV(10) and UDPLITE_RECV_CSCOV(11) are the control parameters of socket options configuration function setsockopt(), used for setting the Checksum Coverage value in the sender and the receiver respectively. Remember that both the sender and the receiver must set the same value, otherwise, the receiver will not be able to verify the checksum properly.

\n","categories":["Study Notes"],"tags":["C/C++ Programming","TCP/IP"]},{"title":"Does Diffie-Hellman Key Exchange Use a Technology Similar to RSA?","url":"/en/2022/11/21/DH-and-RSA/","content":"

Recently, at a WPA3 technology introduction meeting within the R&D team, the speaker mentioned that the OWE technology for encrypted wireless open networks is based on Diffie-Hellman key exchange, and casually said that Diffie-Hellman key exchange is using technology similar to RSA. This statement is wrong! Although Diffie-Hellman key exchange and RSA encryption algorithms belong to public key cryptography, their working mechanisms and application scenarios are different. As a research and development engineer and technician supporting network security, it is necessary to clearly understand the working mechanism and mathematical principles of the two, as well as the differences and connections between them.

\n

A cryptographic system should be secure even if everything about the system, except the key, is public knowledge.
Auguste Kerckhoffs (Dutch linguist and cryptographer, best known for his “Kerckhoffs's principle” of cryptography)

\n
\n

Diffie-Hellman Key Exchange

\n

Diffie-Hellman key exchange (DH for short) is a secure communication protocol that allows two communicating parties to exchange messages over an insecure public channel to create a shared secret without any foreknowledge. This secret can be used to generate keys for subsequent communications between the two parties using symmetric encryption techniques (e.g. AES).

\n

The idea of ​​this kind of public key distribution to achieve shared secrets was first proposed by Ralph Merkle, a doctoral student of Stanford University professor Martin Hellman, and then Professor Hellman's research assistant Whitfield Diffie and Professor Herman jointly invented a practical key exchange protocol. In 1976, Diffie and Hellman were invited to publish their paper \"New Directions in Cryptography\" in IEEE Transactions on Information Theory, which laid the foundation for the public key cryptography system and officially announced the birth of the new Diffie-Herman key exchange technology.

\n

The working principle of Diffie-Hellman key exchange is based on the modular exponentiation operation with the multiplicative group of integers modulo n and its primitive root modulo n in number theory. The following is a simple and specific example to describe:

\n
    \n
  1. Alice chooses a prime number \\(p=71\\), and then a primitive root \\(g=7\\) of the multiplicative group of integers modulo \\(p\\)
  2. \n
  3. Alice chooses a random number \\(a=17\\) that is less than \\(p\\), calculate \\(A=g^a\\bmod\\;p=7^{17}\\bmod\\;71 = 62\\)
  4. \n
  5. Alice sends all \\((p,g,A)\\) to Bob
  6. \n
  7. Bob also chooses a random number \\(b=39\\) that is less than \\(p\\), calculate \\(B=g^b\\bmod\\;p=7^{39}\\bmod\\;71 = 13\\)
  8. \n
  9. Bob sends \\(B\\) back to Alice
  10. \n
  11. Alice calculates \\(s=B^a\\bmod\\;p=13^{17}\\bmod\\;71 = 42\\)
  12. \n
  13. Bob calculate \\(s=A^b\\bmod\\;p=62^{39}\\bmod\\;71 = 42\\)
  14. \n
\n

Is it troublesome calculating \\(\\color{#93F}{\\bf62^{39}\\bmod\\;71}\\)? It is actually very easy……

\n
\n

Remember that modular arithmetic has the property of preserving primitive operations: \\[(a⋅b)\\bmod\\;m = [(a\\bmod\\;m)⋅(b\\bmod\\;m)]\\bmod\\;m\\] Combining with the principle of Exponentiation by Squaring, and applying the right-to-left binary method to do fast calculation: \\[\\begin{align}\n62^{39}\\bmod\\;71 & = (62^{2^0}⋅62^{2^1}⋅62^{2^2}⋅62^{2^5})\\bmod\\;71\\\\\n& = (62⋅10⋅(62^{2^1}⋅62^{2^1})⋅(62^{2^4}⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅(10⋅10)⋅(62^{2^3}⋅62^{2^3}⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅(29⋅29⋅62^{2^3}⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅(60⋅60⋅62^{2^4}))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅(50⋅50))\\bmod\\;71\\\\\n& = (62⋅10⋅29⋅15)\\bmod\\;71\\\\\n& = 42\n\\end{align}\\]

\n\n
\n

As if by magic, both Alice and Bob get the same \\(s\\) value of \\(42\\). This is the shared secret of two people! After this, Alice and Bob can use the hash value of \\(s\\) as a symmetric key for encrypted communication, which is unknown to any third party.

\n

Why? Because of the nature of the modular exponentiation of the multiplicative group, \\(g^{ab}\\) and \\(g^{ba}\\) are equal with the modulo \\(p\\):

\n

\\[A^b\\bmod\\;p=g^{ab}\\bmod\\;p=g^{ba}\\bmod\\;p=B^a\\bmod\\;p\\]

\n

So calculated \\(s\\) values ​​must be the same. Of course, real applications would use much larger \\(p\\), otherwise the attacker can exhaust all the remainder to try to crack the ciphertext encrypted by the symmetric key.

\n

Notice \\((p,g,A,B)\\) is public and \\((a,b,s)\\) is secret. Now suppose an eavesdropper Eve can see all the messages between Alice and Bob, can she deduce \\(s\\)? The answer is that this is only practically possible if the values of \\((p,a,b)\\) are very small. Eve must first invert \\((a,b)\\) from what she knows about \\((p,g,A,B)\\):

\n
    \n
  • \\(A=g^a\\bmod\\;p\\Rightarrow \\color{fuchsia}{a = log_g A\\bmod\\;p}\\)
  • \n
  • \\(B=g^b\\bmod\\;p\\Rightarrow \\color{fuchsia}{b = log_g B\\bmod\\;p}\\)
  • \n
\n

This is the famous discrete logarithm problem. It is a recognized computational challenge and no polynomial-time efficient algorithm is currently found to compute the discrete logarithm. So this protocol is considered eavesdropping-safe as long as the appropriate \\((p,a,b)\\) is chosen. RFC 3526 recommends 6 Modular Exponential (MODP) DH groups of large prime numbers for practical applications, the smallest of which has 1536 bits!

\n

It should also be emphasized that Diffie-Hellman key exchange itself does not require authentication of both communicating parties, so it is vulnerable to man-in-the-middle attacks. If an attacker can tamper with the messages sent and received by both sides in the middle of the channel, he can complete Diffie-Hellman key exchange twice by pretending to be an identity. The attacker can then decrypt the entire message. Therefore, usually practical applications need to incorporate authentication mechanisms to prevent such attacks.

\n

Diffie-Hellman key exchange technique is a crucial contribution to modern cryptography. In 2015, 39 years after the announcement of this invention, Diffie and Hellman jointly won the ACM Turing Award, known as the \"Nobel Prize of Computing\". The ACM award poster directly stated that they \"invented public key cryptography\".

\n

\n

RSA Encryption Algorithm

\n

RSA is a public key encryption algorithm. The public key encryption system with the same name as the core technology is widely used in secure data transmission. Today, the comprehensive development of the Internet has provided great convenience to the public in all aspects of society. Whether you are surfing, gaming, entertaining, shopping, instant messaging with friends and family, managing a bank account, investing in financial securities, or simply sending and receiving email, RSA is working behind the scenes to protect your privacy and data security.

\n

RSA is actually an acronym for the last names of three people: American cryptographer Ronald Rivest, Israeli cryptographer Adi Shamir, and American computer scientist Leonard Max Adleman. In 1977, Levister, Shamir, and Adleman collaborated at the Massachusetts Institute of Technology (MIT) to invent the RSA encryption algorithm. The algorithm was first published in a public technical report at MIT, and later compiled and published in the February 1978 issue of ACM Communications under the title \"A Method for Obtaining Digital Signatures and Public Key Cryptosystems\".

\n

The basic idea of RSA is that the user creates a key pair consisting of a public key and a private key. The public key is freely distributed and the private key must be kept secret. Anyone can encrypt a message with the public key, and the resulting ciphertext can only be deciphered by the private key holder. On the other hand, any message encrypted with the private key can be decrypted by the public key. Since we assume that the private key can only be held by a specific object, encrypting with the private key is equivalent to generating a digital signature, and decrypting with the public key is equivalent to verifying the signature.

\n

The RSA encryption algorithm consists of a four-step operational process: key generation, key distribution, encryption, and decryption. A simple and concrete example is also given below to illustrate.

\n
    \n
  1. Alice randomly chooses two prime numbers \\(p=127\\) and \\(q=5867\\), computes \\(N=pq=745109\\)
  2. \n
  3. Alice computes Carmichael's totient function \\(\\lambda(N)=\\lambda(745109)=52794\\)\n
      \n
    • When \\(p\\) and \\(q\\) are both primes, \\(\\lambda(pq)=\\mathrm{lcm}(p − 1, q − 1)\\)
    • \n
    • \\(\\mathrm{lcm}\\) represents the function for the least common multiple, which may be calculated through the Euclidean algorithm
    • \n
    • \\(\\mathrm{lcm}(126,5866)=52794\\)
    • \n
  4. \n
  5. Alice chooses an integer \\(e=5\\) less than \\(\\lambda(N)\\) but also coprime with \\(\\lambda(N)\\), and calculates the modular multiplicative inverse of \\(e\\) modulo \\(\\lambda(N)\\). That is \\(d\\equiv e^{-1}\\pmod {\\lambda(N)}\\), \\(d=10559\\)\n
      \n
    • The definition of modular multiplicative inverse is, determine \\(d\\) such that \\((d⋅e)\\;\\bmod\\;\\lambda(N)=1\\)
    • \n
    • \\(d=10559\\equiv 5^{-1}\\pmod {52794}\\)
    • \n
  6. \n
  7. \\(\\pmb{(N,e)}\\) is Alice's public key\\(\\pmb{(N,d)}\\) is her private key\n
      \n
    • Alice sends her public key \\((745109,5)\\) to Bob
    • \n
    • Alice saves her private key \\((745109,10559)\\) in a secret place
    • \n
    • Alice distroies all records of \\(p,q,\\lambda(N)\\)
    • \n
  8. \n
  9. When Bob wants to send Alice a message \\(M\\), according to the encoding format agreed upon by both parties, he first translates \\(M\\) to one or more positive integers \\(m\\) that are all less than \\(N\\), and then uses Alice's public key to compute the ciphertext \\(c\\) one by one. The calculation formula is \\(\\pmb{c\\equiv m^e\\pmod N}\\)\n
      \n
    • Assume \\(M\\) is \"CACC 9678\", and the encoding scheme is 0 for spaces, 1-26 for a-z/A-Z (ignoring case), and 27-36 for 0-9
    • \n
    • Encoding yields the positive integer string \"030103 030036 333435\". Note that each integer is less than 745109
    • \n
    • After encryption, it becomes ciphertext integer string \"184539 741303 358095\"\n
        \n
      • \\(184539 \\equiv 30103^5\\pmod {745109}\\)
      • \n
      • \\(741303 \\equiv 30036^5\\pmod {745109}\\)
      • \n
      • \\(358095 \\equiv 333435^5\\pmod {745109}\\)
      • \n
    • \n
  10. \n
  11. After Alice receives the ciphertext integer string, she uses her private key to compute the plaintext one by one, the calculation formula is \\(\\pmb{m\\equiv c^d\\pmod N}\\)\n
      \n
    • \\(30103 \\equiv 184539^{10559}\\pmod {745109}\\)
    • \n
    • \\(30036 \\equiv 741303^{10559}\\pmod {745109}\\)
    • \n
    • \\(333435 \\equiv 358095^{10559}\\pmod {745109}\\)
    • \n
  12. \n
\n

The third step above works out \\(d\\) from \\(\\color{#93F}{\\bf(d\\cdot 5)\\;mod\\;52794=1}\\), here's how

\n
\n

The modular multiplicative invers can be solved quickly by applying the Extended Euclidean algorithm. Referring to this Wiki page, with the precondition of coprime, the following equation can be written (\\(gcd\\) is the function for the greatest common divisor function):

\n

\\[52794s+5t=\\mathrm{gcd}(5, 52794)=1\\]

\n

The goal is to find the smallest positive integer \\(t\\) that satisfies the above equation. The following table shows the iterative process of the algorithm:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Index \\(i\\)Quotient \\(q_{i-1}\\)Remainder \\(r_i\\)\\(s_i\\)\\(t_i\\)
0\\(52794\\)\\(1\\)\\(0\\)
1\\(5\\)\\(0\\)\\(1\\)
2\\(52794 \\div5 = 10558\\)\\(4\\)\\(1 - 10558\\times 0 = 1\\)\\(0 - 10558\\times 1 = -10558\\)
3\\(5 \\div4 = 1\\)\\(1\\)\\(0-1\\times1 = -1\\)\\(1 - 1\\times (-10558) = \\bf10559\\)
\n

It only takes two iterations to get the remainder \\(1\\) and the algorithm ends. The final \\(t\\) is the \\(5^{-1}\\pmod {52794}\\) we want.

\n\n
\n

String together after decoding to get the same information \"CACC 9678\". Why does Alice's decrypted message match exactly the one sent by Bob? The reason lies in the modular exponentiation operation. First of all, because \\(c\\equiv m^e\\pmod N\\), we can get \\(c^d\\equiv (m^e)^d \\equiv m^{ed} \\pmod N\\). Since \\((d⋅e)\\;mod\\;\\lambda(N)=1\\), it is deduced that \\(ed = 1 + h\\lambda(N)\\) (\\(h\\) is a non-negative integer为非负整数). Combine these two

\n

\\[\\Rightarrow m^{ed} = m^{(1+h\\lambda(N))} = \\color{fuchsia}{m(m^{\\lambda(N)})^h \\equiv m(1)^h}\\equiv m\\pmod N\\]

\n

The penultimate congruence above (symbol \\(\\equiv\\)) is based on Euler's theorem). This proves the correctness of the decryption formula \\({m\\equiv c^d\\pmod N}\\)! You can also see that the order of \\(e\\) and \\(d\\) is irrelevant for the result of \\(m^{ed}\\pmod N\\), so the message that Alice encrypted with her private key can be decrypted by Bob with Alice's public key. This also proves the feasibility of digital signatures.

\n

In terms of security, if a third party can derive \\(d\\) from Alice's public key \\((N,e)\\), then the algorithm is broken. But the prerequisite for cracking is to first identify \\(p\\) and \\(q\\) from \\(N\\), which is very difficult when \\(N\\) is big. In fact, this is the famous problem of factoring large numbers, another recognized computational challenge. So far, \"the best-known algorithms are faster than exponential order of magnitude times and slower than polynomial order of magnitude times.\" The latest record, published on the RSA Factoring Challenge website, is the February 2020 crack of RSA-250, a large number of 829 bits. This development indicates that the security of 1024-bit \\(N\\)-valued public keys is already in jeopardy. In view of this, National Institute of Standards and Technology (NIST) recommends that RSA keys be at least 2048 bits in length for real-world applications.

\n

On the other hand, although the public key does not need to be transmitted confidentially, it is required to be reliably distributed. Otherwise, Eve could pretend to be Alice and send her own public key to Bob. If Bob believes it, Eve can intercept all messages passed from Bob to Alice and decrypt them with her own private key. Eve will then encrypt this message with Alice's public key and pass it to her. Alice and Bob cannot detect such a man-in-the-middle attack. The solution to this problem is to establish a trusted third-party authority to issue certificates to ensure the reliability of public keys. This is the origin of the Public Key Infrastructure (PKI).

\n

The RSA public key encryption algorithm is the genius creation of three cryptographers and computer scientists. Its invention is a new milestone in public key cryptography and has become the cornerstone of modern secure Internet communication. The outstanding contribution of Levister, Shamir, and Adelman earned them the ACM Turing Award in 2002, a full 13 years before Diffie and Herman!

\n

\n

Difference and Connection

\n

The following table summarizes the comparison of Diffie-Hellman key exchange and RSA public key encryption algorithm:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Cryptographic TechnologyDiffie-Hellman Key ExchangeRSA Encryption Algorithm
Technology CategoryAsymmetric, Public Key TechnologyAsymmetric, Public Key Technology
Mathematical PrinciplesInteger modulo \\(n\\) multiplicative groups, primitive rootsCarmichael function, modular multiplicative inverse, Euler's theorem
Mathematical OperationsModular exponentiation, exponentiation by squaringModular exponentiation, exponentiation by squaring, extended Euclidean algorithms
Public Key\\((p,g,A,B)\\)\\((N,e)\\)
Private Key\\((a,b,s)\\)\\((N,d)\\)
SecurityDiscrete logarithm problemLarge number prime factorization problem
Typical ApplicationsKey ExchangeEncryption/Decryption, Digital Signature
Key Kength\\(\\ge2048\\) bits\\(\\ge2048\\) bits
AuthenticationRequires external supportRequires PKI support for public key distribution
Forward SecrecySupportNot support
\n

As can be seen, both are asymmetric public key techniques, and both have a public and private key pair. They both use Modular exponentiation and exponentiation by squaring mathematical operations, and the RSA public-key encryption algorithm also requires the application of the extended Euclidean algorithm to solve the modular multiplicative inverse. Despite these similarities, the mathematical principles underlying them are different, and the computational challenges corresponding to their security are different in nature. These characteristics determine that the Diffie-Hellman key exchange can be used for key exchange, but not for encryption/decryption, while the RSA public key encryption algorithm can not only encrypt/decrypt but also support digital signatures. Therefore, the argument that the two use similar technologies cannot be established in general.

\n

ElGamal encryption based on the evolution of the Diffie-Hellman key exchange can be used to encrypt/decrypt messages, but due to some historical reasons and the great commercial success of the RSA public key encryption algorithm, ElGamal encryption is not popular.

\n
\n

In modern cryptography, key length is defined as the number of bits of a key used by an encryption algorithm. Theoretically, since all algorithms may be cracked by brute force, the key length determines an upper limit on the security of an encryption algorithm. Cryptanalytic study shows that the key strengths of Diffie-Hellman key exchange and RSA public key encryption algorithm are about the same. The computational intensities for breaking discrete logarithms and factoring large numbers are comparable. Therefore, the recommended key length for both cryptographic technologies in practical applications is at least 2048 bits.

\n

For authentication, Diffie-Hellman key exchange requires external support, otherwise it is not resistant to man-in-the-middle attacks. RSA public key encryption algorithm can be used to verify digital signatures, but only if there is a PKI supporting reliable public key distribution. The current system of PKI is quite mature, and there is a special Certificate Authority (CA) that undertakes the responsibility of public key legitimacy checking in the public key system, as well as issues and manages public key digital certificates in X.509 format.

\n

One problem with the RSA public key encryption algorithm in practice is that it does not have Forward Secrecy. Forward Secrecy, sometimes referred to as Perfect Forward Secrecy, is a security property of confidential communication protocols, meaning that the leakage of the long-term used master key does not result in the leakage of past session information. If the system has forward secrecy, it can protect the historical communication records in case of private key leakage. Imagine a situation where, although Eve cannot decrypt the RSA-encrypted messages between Alice and Bob, Eve can archive the entire past message ciphertext. One day in the future, Alice's private key for some reason was leaked, then Eve can decrypt all the message records.

\n

The solution to this problem is Diffie-Hellman key exchange! Remember that the \\((A,B)\\) in the public key of the Diffie-Hellman key exchange is generated by both parties from their respective private keys \\((a,b)\\), so if a random \\((a,b)\\) value is generated at each session, future key leaks will not crack the previous session key. This shows that Diffie-Hellman key exchange supports forward secrecy! If we combine the forward secrecy of Diffie-Hellman key exchange with the digital signature feature of the RSA public key encryption algorithm, we can implement a key exchange with authentication protection. This process can be simplified by the following example.

\n
    \n
  1. Alice and Bob exchange authenticated RSA public key certificates
  2. \n
  3. Alice and Bob each generate a random \\((a,b)\\) value and compute \\((A,B)\\) using the shared Diffie-Hellman \\((p,g)\\).
  4. \n
  5. Alice encrypts \\(A\\) with her RSA private key to generate a digital signature, which she sends to Bob along with \\(A\\)
  6. \n
  7. Bob encrypts \\(B\\) with his own RSA private key to generate a digital signature and sends it to Alice along with \\(B\\).
  8. \n
  9. Alice verifies the signature with Bob's RSA public key, confirms that \\(B\\) came from Bob, and computes \\(s\\) using \\((p,a,B)\\). 6.
  10. \n
  11. Bob verifies the signature with Alice's RSA public key, confirms that \\(A\\) came from Alice, and computes \\(s\\) using \\((p,b,A)\\)
  12. \n
  13. Alice and Bob agree to share a secret and generate a subsequent symmetric encryption (AES) session key for confidential communication
  14. \n
\n

Here the RSA digital signature safeguards the key exchange from man-in-the-middle attacks. Also in the second step above, if a new random number is generated for each session, then even if Alice's or Bob's RSA private keys are leaked one day, it does not threaten the security of previous sessions because the eavesdropper still has to solve the discrete logarithm puzzle. We have also achieved forward secrecy. In fact, this is the working mechanism of the DHE-RSA cipher suite as defined by the ubiquitous Transport Layer Security (TLS) protocol.

\n

DHE-RSA Cipher Suite

\n

Transport Layer Security (TLS) and its predecessor Secure Sockets Layer (SSL) is a security protocol that provides security and data integrity for Internet communications. TLS is widely used in applications such as browsers, email, instant messaging, VoIP, and virtual private networks (VPNs), and has become the de facto industry standard for secure Internet communications. Currently, TLS 1.2 is the commonly supported version of the protocol, supporting secure connections over TCP. Datagram Transport Layer Security (DTLS) protocol is also defined for UDP applications. DTLS is much the same as TLS, with some extensions for connectionless UDP transport in terms of reliability and security. DTLS 1.2 matches the functionality of TLS 1.2.

\n

The TLS protocol uses a client-server architectural model. It works by using X.509 authentication and asymmetric encryption algorithms to authenticate the communicating parties, after which keys are exchanged to generate a symmetric encryption session key. This session key is then used to encrypt the data exchanged between the two communicating parties, ensuring the confidentiality and reliability of the information without fear of attack or eavesdropping by third parties. For identification purposes, the TLS 1.2 protocol combines the authentication, key exchange, bulk encryption, and message authentication code algorithms used into the Cipher Suite name. Each Cipher Suite is given a double-byte encoding. The TLS Cipher Suite Registry provides a reference table of all registered Cipher Suite names, sorted by encoding value from small to large.

\n

Since the computation intensity of asymmetric encryption algorithms (RSA, etc.) is much higher than that of symmetric encryption algorithms (AES, etc.), practical applications almost always use symmetric encryption algorithms to encrypt messages in batches in terms of performance.

\n
\n

TLS 1.2 protocol supports a series of cipher suites that combine the Diffie-Hellman key exchange with the RSA public key encryption algorithm. They all start with TLS_DH_RSA or TLS_DHE_RSA`. The \"E\" in DHE stands for \"Ephemeral\", which means that a random \\((a,b)\\) value is required to be generated for each session. So TLS_DHE_RSA cipher suite can provide forward secrecy, while TLS_DH_RSA cannot, and the former should be preferred in practical applications.

\n

Here we take a typical TLS_DHE_RSA_WITH_AES_128_CBC_SHA (encoding 0x00,0x33) cipher suite as an example to explain the process of Diffie-Hellman working with RSA to establish a DTLS session. First, explain the composition of the cipher suite.

\n
    \n
  • DHE: ephemeral DH to implement key exchange
  • \n
  • RSA: public key for signing and certifying the DHE
  • \n
  • AES_128_CBC: 128-bit CBC mode AES encryption
  • \n
  • SHA: 160-bit HMAC-SHA1 hash message authentication code
  • \n
\n

Referring to the packet file dtls-dhe-rsa.pcap captured from the network port, the following handshake protocol message sequence chart can be obtained

\n
\nsequenceDiagram\n\nautonumber\nparticipant C as Client\nparticipant S as Server\nNote over C,S: Handshake Protocol\nrect rgb(230, 250, 255)\nC->>S: Client Hello (Cr, Cipher Suites))\nS-->>C: Hello Verify Request (Cookie)\nC->>S: Client Hello (Cr, Cookie, Cipher Suites)\nS-->>C: Server Hello (Sr, Cipher Suite), Certificate (Sn, Se)\nS-->>C: Server Key Exchange (p,g,A,Ss)\nS-->>C: Certificate Request, Server Hello Done\nC->>S: Certificate (Cn, Ce)\nC->>S: Client Key Exchange (B)\nC->>S: Certificate Verify (Cs)\nend\nNote over C,S: Establish Secure Channel\nrect rgb(239, 252, 202)\nC->>S: Change Cipher Spec, Encrypted Handshake Message\nS-->>C: Change Cipher Spec, Encrypted Handshake Message\nC->>S: Application Data\nS-->>C: Application Data\nend\n \n
\n

Below is the analysis with regard to the data package numbers in the message sequence chart:

\n
    \n
  • Packets \\(\\require{enclose}\\enclose{circle}{1}-\\enclose{circle}{3}\\) present the initial handshake message exchange.\n
      \n
    • The client first sends a Hello message containing a random number \\(C_r\\) and a list of supported cipher suites
    • \n
    • The server responds with a Hello Verify Request message containing a block of information (cookie)
    • \n
    • The client receives the Hello Verify Request and resends the Hello message with the entire contents of the previous message plus a copy of the cookie
    • \n
  • \n
\n

Hello verification is specific to DTLS to prevent denial of service attacks. The protocol stipulates that the server will not continue to serve the client until it receives a hello message containing the copied cookie.

\n
\n
    \n
  • Packets \\(\\require{enclose}\\enclose{circle}{4}-\\enclose{circle}{6}\\) shows the server enters verification and key exchange stage:\n
      \n
    • The server responds with a Hello message first, which contains the random number \\(S_r\\) and the selected cipher suite\n
        \n
      • As shown below, the server selects TLS_DHE_RSA_WITH_AES_128_CBC_SHA!
      • \n
    • \n
    • The same packet also contains the Server Certificate message, which is typically large and divided into multiple fragments
    • \n
    • The server certificate provides the RSA public key \\((S_N,\\;S_e)\\) that verifies its signature
    • \n
    • Next, the server sends a Key Exchange message containing its DH public key \\((p,g,A)\\) and signature \\(Ss\\)\n
        \n
      • The length of \\(p\\) in the figure below is 256 bytes, which means that the key length is 2048 bits and \\(Pubkey\\) is \\(A\\).
      • \n
      • You can also see in the figure that the algorithms chosen for the signature are SHA512 and RSA.
      • \n
      • The operation is to first compute \\(\\operatorname{SHA512}(Cr,Sr,p,g,A)\\) and then encrypt it with the server RSA private key
      • \n
    • \n
    • After that, the server sends a Certificate Request message and a Hello Done message\n
        \n
      • The server requests the client to send an RSA public key certificate that verifies its signature
      • \n
    • \n
  • \n
\n

Note: If DH-RSA cipher suite is used, the server-side DH public key parameters \\((p,g,A)\\) are unchanged and will be included directly in its certificate message. At this time, the server will not issue a Key Exchange message \\(\\require{enclose}\\enclose{circle}{5}\\). For DHE-RSA, the \\(A\\) value is different for each session.

\n
\n
    \n
  • Packets \\(\\require{enclose}\\enclose{circle}{7}-\\enclose{circle}{9}\\) shows the client enters verification and key echange stage:\n
      \n
    • The client first sends a Certificate message, which contains the RSA public key \\((C_N,\\;C_e)\\) and also splits into multiple fragments
    • \n
    • The client then sends a Key Exchange message, which contains its DH public key \\(B\\)\n
        \n
      • The \\(Pubkey\\) in the following figure is \\(B\\)
      • \n
    • \n
    • The client finally sends a Certificate Verify message, which contains the signature \\(Cs\\)\n
        \n
      • The signature covers all previous messages except for the initial Client Hello \\(\\require{enclose}\\enclose{circle}{1}\\) and the Hello Verify Request \\(\\require{enclose}\\enclose{circle}{2}\\)
      • \n
      • The signature operation also computes SHA512 and encrypts it with the client's RSA private key
      • \n
    • \n
  • \n
  • Packets \\(\\require{enclose}\\enclose{circle}{10}-\\enclose{circle}{11}\\) completes handshake and establishs the secure channel:\n
      \n
    • Each side first verifies the signature sent by the other side
    • \n
    • After successful verification, DH algorithm is run to generate the same premaster key
    • \n
    • Both parties call pseudo-random function (PRF) to generate a 48-byte master key from the premaster key \\[master\\_secret = \\operatorname{PRF}(pre\\_master\\_secret,\\unicode{x201C}master\\;secret\\unicode{x201D},Cr+Sr)[0..47]\\]
    • \n
    • Both parties call PRF again to generate a 72-byte key block from the master key \\[key\\_block = \\operatorname{PRF}(master\\_secret,\\unicode{x201C}key\\;expansion\\unicode{x201D},Sr+Cr)[0..71]\\]
    • \n
    • Key blocks are assigned to HMAC-SHA1 and AES_128_CBC function blocks.\n
        \n
      • Client Write Message Authentication Code (MAC) key: 20 bytes
      • \n
      • Server Write Message Authentication Code (MAC) key: 20 bytes
      • \n
      • Client Write Encryption Key: 16 bytes
      • \n
      • Server write encryption key: 16 bytes
      • \n
      \nNote that TLS/DTLS 1.2 specifies that this cipher suite uses an explicit initial vector (IV) and does not require the allocation of a key block
    • \n
    • The client generates a Change Cipher Spec message indicating the start of the encryption and MAC modules
    • \n
    • The client invokes PRF a third time to generate the 12-byte end-of-handshake authentication code used for master key and handshake message authentication, which is packaged into an end-of-handshake message and entered into the encryption and MAC modules \\[\\operatorname{PRF}(master\\_secret,finished\\_label,\\operatorname{SHA256}(handshake\\_messages))[0..11]\\]
    • \n
    • The client sends the Change Cipher Spec message and the encrypted end-of-handshake message to the server
    • \n
    • The server verifies the received client end-of-handshake message and repeats the above three steps to generate its own Change Cipher Spec message and encrypted an end-of-handshake message, then send them to the client
    • \n
    • The client completes the handshake by verifying the received server end-of-handshake message. Now the encrypted secure channel is established
    • \n
  • \n
  • Packets \\(\\require{enclose}\\enclose{circle}{12}-\\enclose{circle}{13}\\) shows that the encrypted application data exchange has officially started
  • \n
\n

This is the complete process of establishing a secure message channel using the TLS_DHE_RSA_WITH_AES_128_CBC_SHA (encoding 0x00,0x33) cipher suite, where DHE implements a key exchange with forward secrecy protection and RSA digital signature provides authentication for DHE, creating a solution for secure communication. With a clear understanding of this, we will better grasp the working mechanism of Diffie-Hellman and RSA, effectively apply them in practice and avoid unnecessary mistakes.

\n","categories":["Study Notes"],"tags":["Cryptography","Network Security"]},{"title":"Understand Endianness","url":"/en/2021/12/24/Endianness/","content":"

The problem of Endianness is essentially a question about how computers store large numbers.

\n

I do not fear computers. I fear lack of them.
Isaac Asimov (American writer and professor of biochemistry, best known for his hard science fiction)

\n
\n

We know that one basic memory unit can hold one byte, and each memory unit has its address. For an integer larger than decimal 255 (0xff in hexadecimal), more than one memory unit is required. For example, 4660 is 0x1234 in hexadecimal and requires two bytes. Different computer systems use different methods to store these two bytes. In our common PC, the least-significant byte 0x34 is stored in the low address memory unit and the most-significant byte 0x12 is stored in the high address memory unit. While in Sun workstations, the opposite is true, with 0x34 in the high address memory unit and 0x12 in the low address memory unit. The former is called Little Endian and the latter is Big Endian.

\n

How can I remember these two data storing modes? It is quite simple. First, remember that the addresses of the memory units we are talking about are always arranged from low to high. For a multi-byte number, if the first byte in the low address you see is the least-significant byte, the system is Little Endian, where Little matches low. On the contrary is Big Endian, where Big corresponds to \"high\".

\n

Program Example

\n

To deepen our understanding of Endianness, let's look at the following example of a C program:

\n
char a = 1; \t \t \t 
char b = 2;
short c = 255;\t/* 0x00ff */
long d = 0x44332211;
\n

On Intel 80x86 based systems, the memory content corresponding to variables a, b, c, and d are shown in the following table:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Address OffsetMemory Content
0x000001 02 FF 00
0x000411 22 33 44
\n

We can immediately tell that this system is Little Endian. For a 16-bit integer short c, we see the least-significant byte 0xff first, and the next one is 0x00. Similarly for a 32-bit integer long d, the least-significant byte 0x11 is stored at the lowest address 0x0004. If this is in a Big Endian computer, memory content would be 01 02 00 FF 44 33 22 11.

\n

At the run time all computer processors must choose between these two Endians. The following is a shortlist of processor types with supported Endian modes:

\n
    \n
  • Pure Big Endian: Sun SPARC, Motorola 68000, Java Virtual Machine
  • \n
  • Bi-Endian running Big Endian mode: MIPS with IRIX, PA-RISC, most Power and PowerPC systems
  • \n
  • Bi-Endian running Little Endian mode: ARM, MIPS with Ultrix, most DEC Alpha, IA-64 with Linux
  • \n
  • Little Endian: Intel x86, AMD64, DEC VAX
  • \n
\n

How to detect the Endianess of local system in the program? The following function can be called for a quick check. If the return value is 1, it is Little Endian, else Big Endian

\n
int test_endian() {
int x = 1;
return *((char *)&x);
}
\n

Network Order

\n

Endianness is also important for computer communications. Imagine that when a Little Endian system communicates with a Big Endian system, the receiver and sender will interpret the data completely differently if not handled properly. For example, for the variable d in the C program segment above, the Little Endian sender sends 11 22 33 44 four bytes, which the Big Endian receiver converts to the value 0x11223344. This is very different from the original value. To solve this problem, the TCP/IP protocol specifies a special \"network byte order\" (referred to as \"network order\"), which means that regardless of the Endian supported by the computer system, the most-significant byte is always sent first while transmitting data. From the definition, we can see that the network order corresponds to the Big Endian.

\n

To avoid communication problems caused by Endianness and to facilitate software developers to write portable programs, some C preprocessing macros are defined for conversion between network bytes and local byte order. htons() and htonl() are used to convert local byte order to network byte order, the former works with 16-bit unsigned numbers and the latter for 32-bit unsigned numbers. ntohs() and ntohl() implement the conversion in the opposite direction. The prototype definitions of these four macros can be found as follows (available in the netinet/in.h file on Linux systems).

\n
#if defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN)

#define htons(A) (A)
#define htonl(A) (A)
#define ntohs(A) (A)
#define ntohl(A) (A)

#elif defined(LITTLE_ENDIAN) && !defined(BIG_ENDIAN)

#define htons(A) ((((uint16)(A) & 0xff00) >> 8) | \\
(((uint16)(A) & 0x00ff) << 8))
#define htonl(A) ((((uint32)(A) & 0xff000000) >> 24) | \\
(((uint32)(A) & 0x00ff0000) >> 8) | \\
(((uint32)(A) & 0x0000ff00) << 8) | \\
(((uint32)(A) & 0x000000ff) << 24))
#define ntohs htons
#define ntohl htohl

#else

#error "Either BIG_ENDIAN or LITTLE_ENDIAN must be #defined, but not both."

#endif
\n","categories":["Study Notes"],"tags":["C/C++ Programming","System Programming","Computer Architecture","Computer Communications"]},{"title":"IPv6 Dynamic Address Allocation Mechanism Illustrated","url":"/en/2022/03/13/IPv6-Addressing/","content":"

IPv6 supports multiple addresses, making address assignments more flexible and convenient. Unlike IPv4, which relied solely on the DHCP protocol for address assignment, IPv6 incorporates a native Stateless Address AutoConfiguration SLAAC) protocol. SLAAC can either work alone to provide IPv6 addresses to hosts, or it can work with DHCPv6 to generate new assignment schemes. Here is a comprehensive analysis of the dynamic address allocation mechanism for IPv6.

\n

Who the hell knew how much address space we needed?
Vint Cerf (American Internet pioneer and one of \"the fathers of the Internet\")

\n
\n

IPv6 Address Overview

\n

Address Formats

\n

The most significant difference between IPv6 and IPv4 is its large address space. IPv4 has 32 bits (4 bytes) and allows for approximately 4.29 (232) billion addresses. IPv6, on the other hand, defines 128 bits (16 bytes) and supports approximately 340 x 1036 addresses. This is a pretty impressive number, and there will be no address depletion for the foreseeable future. A typical IPv6 address can be divided into two parts. As shown in the figure below, the first 64 bits are used to represent the network, and the next 64 bits are used as the interface identifier.

\n

The interface identifier can be generated in several ways:

\n\n

IETF recommends a canonical textual representation format for ease of writing. It includes leading zeros suppression and compression of consecutive all-zero fields. With the network prefix length at the end, the above address can be shortened to 2001:db8:130f::7000:0:140b/64.

\n

Address Types

\n

RFC 4291 defines three types of addresses:

\n
    \n
  1. Unicast: A network address corresponds to a single network node, point-to-point connection.
  2. \n
  3. Anycast: The target address corresponds to a group of receiving nodes, but only the \"nearest\" one receives.
  4. \n
  5. Multicast: The target address corresponds to a group of nodes that can receive replicated messages.
  6. \n
\n

Note that there are no broadcast addresses in IPv6, their function being superseded by multicast addresses. Anycast addresses are syntactically indistinguishable from unicast addresses and have very limited applications. A typical application for anycast is to set up a DNS root server to allow hosts to look up domain names in close proximity. For unicast and multicast addresses, they can be identified by different network prefixes:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Address TypeBinary FormHexadecimal FormApplication
Link-local address (unicast)1111 1110 10fe80::/10Use on a single link, non-routable
Unique local address (unicast)1111 1101fd00::/8Analogous to IPv4 private network addressing
Global unicast address0012000::/3Internet communications
Multicast address1111 1111ff00::/8Group communications, video streaming
\n

Each interface of a host must have a link-local address. Additionally, it can be manually or dynamically autoconfigured to obtain a unique local address and a global unicast address. Thus, IPv6 interfaces naturally have multiple unicast addresses. Unique local addresses are managed by the local network administrator, while the global unicast addresses are allocated by the IANA-designated regional registry. Referring to the following diagram, all current global unicast addresses are assigned from the 2000::/3 address block, with the first 48 bits of the address identifying the service provider's global routing network and the next 16 bits identifying the enterprise or campus internal subnet: Because an IPv6 multicast address can only be used as a destination address, its bit definition is different from that of unicast. Referring to RFC 4291, a multicast address containing 4 bits of the feature flags, 4 bits of the group scope, and the last 112 bits of the group identifier: Furthermore the same protocol specifies a few pre-defined IPv6 multicast addresses, the most important of which are

\n
    \n
  • All Nodes Addresses on the local link — ff02::1
  • \n
  • All Routers Addresses on the local link — ff02::2
  • \n
  • Solicited-Node Address on local link — ff02::1:ffxx:xxxx
  • \n
\n

Dynamic Allocation Schemes

\n

NDP Protocol

\n

IPv6 dynamic address assignment depends on Neighbor Discovery Protocol (NDP). NDP acts at the data link layer and is responsible for discovering other nodes and corresponding IPv6 addresses on the link and determining available routes and maintaining information reachability to other active nodes. It provides the IPv6 network with the equivalent of the Address Resolution Protocol (ARP) and ICMP router discovery and redirection protocols in IPv4 networks. However, NDP adds many improvements and new features. NDP defines five ICMPv6 message types:

\n
    \n
  1. Router Solicitation (RS)
  2. \n
  3. Router Advertisement (RA)
  4. \n
  5. Neighbor Solicitation (NS)
  6. \n
  7. Neighbor Advertisement (NA)
  8. \n
  9. Redirect
  10. \n
\n

The first two message types here, RS and RA, are the keys to implementing dynamic IPv6 address assignment. The host sends an RS message to the multicast address ff02::2 of all routers in the local network segment to request routing information. When the router receives the RS from the network node, it sends an immediate RA in response. The message format of the RA is as follows

\n
 0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Code | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Cur Hop Limit |M|O| Reserved | Router Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reachable Time |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Retrans Timer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options ...
+-+-+-+-+-+-+-+-+-+-+-+-
\n

It defines two special bits, M and O, with the following meaning:

\n
    \n
  • M — \"Managed address configuration\" flag, set to 1 when the address is obtained from DHCPv6.
  • \n
  • O — \"Other configuration\" flag, set to 1 to indicate that other configuration information is available via DHCPv6
  • \n
\n

The RA message ends with the Options section, which originally had three possible options: Source Link-Layer Address, MTU, and Prefix Information. Later, RFC 8106 (which replaced RFC 6106) added the Recursive DNS Server (RDNSS) and DNS Search List (DNSSL) options. The Prefix Information option directly provide hosts with on-link prefixes and prefixes for Address Autoconfiguration, and it has the following format

\n
 0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length | Prefix Length |L|A| Reserved1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Valid Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Preferred Lifetime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reserved2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Prefix +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
\n

Here the Prefix Length and the Prefix jointly determine the network prefix of the IPv6 address. In addition, the Prefix Information option also defines two special bits, L and A:

\n
    \n
  • L — on-link flag. When set, indicates that this prefix can be used for on-link determination.
  • \n
  • A — autonomous address-configuration flag. When set, indicates that this prefix can be used for SLAAC.
  • \n
\n

Similar to the IPv4 subnet mask feature, the purpose of the \"on-link\" determination is to allow the host to determine which networks an interface can access. By default, the host only considers the network where the link-local address is located as \"on-link\". If the \"on-link\" status of a destination address cannot be determined, the host forwards the IPv6 datagram to the default gateway (or default router) by default. When the host receives an RA message, if the \"on-link\" flag for a prefix information option is set to 1 and the Valid Lifetime is also a non-zero value, the host creates a new prefix network entry for it in the prefix list. All unexpired prefix network entries are \"on-link\".

\n

Message Sequence

\n

After understanding the NDP protocol and the information conveyed by the RA messages, let's see how they guide the network nodes to achieve dynamic address assignment.

\n

Routers in the network periodically send RA messages to the multicast addresses (ff02::1) of all nodes in the local subnet. However, to avoid latency, the host sends one or more RS messages to all routers in the local subnet as soon as it has finished booting. The protocol requires the routers to respond to the RA messages within 0.5 seconds. Then, based on the values of the M/O/A bits in the received RA messages, the host decides how to dynamically configure the unique local and global unicast addresses of the interface and how to obtain other configuration information. With certain combinations of bit fetch values, the host needs to run DHCPv6 client software to connect to the server to obtain address assignment and/or other configuration information. The entire process is shown in the following message sequence diagram.

\n
\nsequenceDiagram\n\nparticipant R as Router\nparticipant H as Host\nparticipant S as DHCPv6 Server\nNote over R,H: Router Request\nrect rgb(239, 252, 202)\nH->>R: Router Solicitation\nR-->>H: Router Advertisement\nend\nNote over H,S: Address Request\nrect rgb(230, 250, 255)\nH->>S: DHCPv6 Solicit\nS-->>H: DHCPv6 Advertise\nH->>S: DHCPv6 Request\nS-->>H: DHCPv6 Reply\nend\nNote over H,S: Other Information Request\nrect rgb(230, 250, 255)\nH->>S: DHCPv6 Information-request\nS-->>H: DHCPv6 Reply\nend\n\n
\n

Note: Unlike the IPv4 DHCP protocol, DHCPv6 clients use UDP port 546 and servers use UDP port 547.

\n
\n

Next explain in detail three dynamic allocation schemes determined by the combination of the M/O/A-bit values:

\n
    \n
  • SLAAC
  • \n
  • SLAAC + Stateless DHCPv6
  • \n
  • Stateful DHCPv6
  • \n
\n

SLAAC

\n

SLAAC is the simplest automatic IPv6 address assignment scheme and does not require any server. It works by sending an RS message request after the host starts up and the router sends back RA messages to all nodes in the local network segment. If the RA message contains the following configuration

\n
    \n
  • M-bit and O-bit all clear in the message header
  • \n
  • L-bit and A-bit all set in Prefix Information option
  • \n
\n

Then the host receives this RA message and performs the following operations to implement SLAAC:

\n
    \n
  1. Combine the network prefix with the local interface identifier to generate a unique local address or global unicast address.
  2. \n
  3. Install the default gateway (or default route) to point to the router address (source address of the RA message).
  4. \n
  5. Set this interface as the \"on-link\" corresponding to the network prefix, which is also the next-hop interface of the default gateway above.
  6. \n
  7. If the RDNSS and/or DNSSL options are included, install the name servers and domain name suffixes.
  8. \n
\n

This way, the host gets one or more IPv6 unique local addresses or global unicast addresses, plus the default gateway and domain name service information to complete various Internet connections.

\n

The following is an example of the SLAAC configuration on a Cisco Catalyst 9300 Multilayer Access Switch:

\n
ipv6 unicast-routing
interface Vlan10
ipv6 enable
ipv6 address 2001:ABCD:1000::1/64
ipv6 nd ra dns server 2001:4860:4860::8888 infinite
ipv6 nd ra dns search-list example.com
\n

The Layer 3 interface of the Cisco Multilayer Switch provides routing functionality. As you can see, when IPv6 is activated on the Layer 3 interface in VLAN 10, its default address auto-assignment scheme is SLAAC. the control bits of RA messages from this interface are all set according to the SLAAC scheme, and the network prefixes for each IPv6 address it configures are automatically added to the RA prefix information options list. Of course, the network administrator can also exclude certain network prefixes with a separate interface configuration command. The last two lines of the example configuration command specify RDNSS and DNSSL, which are also added to the RA message options.

\n

If a host connects to a port in VLAN 10, it immediately gets a global unicast address with the network prefix of 2001:ABCD:1000::/64, and its default gateway address is set to 2001:ABCD:1000::1. Open a browser and enter a URL, and it will send a message to the specified domain name server 2001:4860:4860::8888 (Google's public name server address) to obtain the IPv6 address of the destination URL to establish a connection.

\n

SLAAC + Stateless DHCPv6

\n

SLAAC automatic address assignment is fast and easy, providing a plug-and-play IPv6 deployment solution for small and medium-sized network deployments. However, if a network node needs access to additional configuration information, such as NTP/SNTP server, TFTP server, and SIP server addresses, or if its functionality relies on certain Vendor-specific Information Options, it must choose SLAAC + stateless DHCPv6 scheme.

\n

This scenario still uses SLAAC automatic address assignment, but the router instructs the host to connect to a DHCPv6 server for additional configuration information. At this point, the RA message sent back by the router has

\n
    \n
  • M-bit clear and O-bit set in the message header
  • \n
  • L-bit and A-bit all set in Prefix Information option
  • \n
\n

After receiving this RA message, the host performs the following actions:

\n
    \n
  1. Combine the network prefix with the local interface identifier to generate a unique local address or global unicast address.
  2. \n
  3. Install a default gateway (or default route) pointing to the router address (source address of the RA message).
  4. \n
  5. Set this interface as the \"on-link\" corresponding to the network prefix, which is also the next-hop interface of the default gateway above.
  6. \n
  7. If the RDNSS and/or DNSSL options are included, install the name servers and domain name suffixes.
  8. \n
  9. Start the DHCPv6 client and connect to the DHCPv6 server to request additional configuration information.
  10. \n
  11. Save the additional configuration information replied by the DHCPv6 server.
  12. \n
\n

As you can see, SLAAC + stateless DHCPv6 is not different from SLAAC in terms of address assignment. DHCPv6 only provides additional configuration information and does not assign IPv6 addresses. So the DHCPv6 server does not track the address assignment status of network nodes, which is what \"stateless\" means.

\n

The corresponding configuration commands on the Catalyst 9300 switch are as follows.

\n
ipv6 unicast-routing
ipv6 dhcp pool vlan-10-clients
dns-server 2001:4860:4860::8888
domain-name example.com
sntp address 2001:DB8:2000:2000::33
interface Vlan10
ipv6 enable
ipv6 address 2001:ABCD:1000::1/64
ipv6 nd other-config-flag
ipv6 dhcp server vlan-10-clients
# ipv6 dhcp relay destination 2001:9:6:40::1
\n

The difference with the SLAAC example is that the VLAN 10 interface configuration command ipv6 nd other-config-flag explicitly specifies to set the O-bit of the RA message. Its next command, ipv6 dhcp server vlan-10-clients, activates the DHCPv6 server response feature of the interface, corresponding to the server's pool name of vlan-10-clients. The DHCPv6 server is configured above the interface configuration, starting at ipv6 dhcp pool vlan-10-clients, and contains the DNS server address, DNS domain name, and SNTP server address.

\n

If you are using a separate DHCPv6 server located on a network segment, you can remove the ipv6 dhcp server command and enable the ipv6 dhcp relay destination command on the next line of the example to specify the address to forward DHCPv6 requests to the external server.

\n

Stateful DHCPv6

\n

Many large enterprises use DHCP to manage the IPv4 addresses of their devices, so deploying DHCPv6 to centrally assign and manage IPv6 addresses is a natural preference. This is where Stateful DHCPv6 comes into play. This scenario also requires RA messages sent by the router but does not rely solely on network prefixes for automatic address assignment. The control bits of the RA messages are configured to

\n
    \n
  • M-bit set in the message header, O-bit does not matter
  • \n
  • L-bit and A-bit can be set or clear as desired in Prefix Information option
  • \n
\n

Upon receiving this RA message, the host performs the following actions:

\n
    \n
  1. Generate a unique local address or a global unicast address if there is a Prefix Information option with the A-bit set.
  2. \n
  3. Install a default gateway (or default route) pointing to the router address (source address of the RA message).
  4. \n
  5. If there is a Prefix Information option with the L-bit set, set this interface to \"on-link\" with the corresponding network prefix.
  6. \n
  7. If the RDNSS and/or DNSSL options are included, install the name servers and domain suffixes.
  8. \n
  9. Start the DHCPv6 client and connect to the server to request addresses and other configuration information.
  10. \n
  11. Set the address assigned by the DHCPv6 server to this interface.
  12. \n
  13. Save additional configuration information from the DHCPv6 server response.
  14. \n
\n

An example of the Stateful DHCPv6 configuration command on a Catalyst 9300 switch is as follows.

\n
ipv6 unicast-routing
ipv6 dhcp pool vlan-10-clients
address prefix FD09:9:5:90::/64
address prefix 2001:9:5:90::/64
dns-server 2001:9:5:90::115
domain-name test.com
interface Vlan10
ipv6 enable
ipv6 address 2001:ABCD:1:1::1/64
ipv6 nd prefix 2001:ABCD:1:1::/64 no-advertise
ipv6 nd managed-config-flag
ipv6 dhcp server vlan-10-clients
\n

Compared to SLAAC + Stateless DHCPv6, the interface configuration here removes the ipv6 nd other-config-flag and replaces it with the ipv6 nd managed-config-flag command. This corresponds to setting the M-bit of the RA message header. The DHCPv6 server configuration adds two address prefix commands to set the network prefix. Also, the ipv6 nd prefix 2001:ABCD:1:1::/64 no-advertise configured for the interface specifies that the router does not include the 2001:ABCD:1:1::/64 prefix information option into the RA. So, this example host interface will not generate SLAAC addresses, but only two addresses from DHPCv6: a unique local address with the network prefix FD09:9:5:90::/64, and a global unicast address with the network prefix 2001:9:5:90::/64. The interface identifier for each of these two addresses is also specified by DHPCv6.

\n

How to distinguish the source of dynamically assigned addresses for host interfaces? The method is simple. One thing to remember is that DHPCv6 does not send the network prefix length to the requestor, so the network prefix length of the addresses received from DHPCv6 is 128, while the network prefix length of the addresses generated by SLAAC will not be 128. See the following example of the wired0 interface on a Linux host:

\n
ifconfig wired0
wired0 Link encap:Ethernet HWaddr A0:EC:F9:6C:D9:30
inet6 addr: 2001:20::53c7:1364:a4d8:fd91/128 Scope:Global
inet6 addr: 2001:20::a2ec:f9ff:fe6c:d930/64 Scope:Global
inet6 addr: fe80::a2ec:f9ff:fe6c:d930/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:510 errors:0 dropped:0 overruns:0 frame:0
TX packets:1213 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:93670 (91.4 KiB) TX bytes:271979 (265.6 KiB)
\n

We can immediately determine that the interface is using Stateful DHCPv6 address assignment, but also generates the SLAAC address with the same network prefix 2001:20::/64 received.

\n
    \n
  • 2001:20::53c7:1364:a4d8:fd91/128 — DHCPv6 address, random interface identifer
  • \n
  • 2001:20::a2ec:f9ff:fe6c:d930/64 — SLAAC addeess, interface identifer is MAC in EUI-64 format
  • \n
  • fe80::a2ec:f9ff:fe6c:d930/64 — Link-local address, interface identifer is MAC in EUI-64 format
  • \n
\n

Note: DHPCv6 server also does not provide any IPv6 default gateway information. The host needs to be informed of the dynamic default gateway from the RA message.

\n
\n

Summary and Comparison

\n

The following table shows the control bit combinations of RA messages concerning different address allocation and other configuration acquisition methods.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
M-bitO-bitA-bitHost AddressOther Configuration
000Static SettingsManual Configuration
001Prefix specified by RA, automatically generatedmanually configured
010Static SettingsDHCPv6
011Prefix specified by RA, automatically generatedDHCPv6
100Stateful DHCPv6DHCPv6
101Stateful DHCPv6 and/or automatically generatedDHCPv6
110Stateful DHCPv6DHCPv6
111Stateful DHCPv6 and/or automatically generatedDHCPv6
\n

Summarize three dynamic allocation schemes:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Allocation SchemeFeaturesAppiccation Scenarios
SLAACSimple and practical, fast deploymentSMB, Consumer Product Networking, Internet of Things (IoT)
SLAAC + Stateless DHCPv6Auto Configuration, Extended ServicesSMBs need additional network services
Stateful DHCPv6Centralized management and controlLarge enterprises, institutions, and campus networks
\n

Note: Since IPv6 network interfaces can have multiple addresses (a link-local address, plus one or more unique local addresses and/or global unicast addresses), it becomes important how the source address is selected when establishing an external connection. RFC 6724 gives detailed IPv6 source address selection rules. In the development of embedded systems, the control plane and the data plane connected to the same remote device are often implemented by different functional components. For example, the control plane directly calls a Linux userspace socket to establish the connection, and the IPv6 source address used for the connection is selected by the TCP/IP stack, while the data plane directly implements data encapsulation processing and transmission in kernel space. In this case, the IPv6 source address selected by the control plane has to be synchronized to the data plane in time, otherwise, the user data might not be delivered to the same destination.

\n
\n

Troubleshooting Guide

\n

The common IPv6 dynamic address assignment debugging and troubleshooting commands on Cisco routers and switches are listed in the following table.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
CommandDescription
show ipv6 interface briefDisplays a short summary of IPv6 status and configuration for each interface
show ipv6 interface [type] [num]Displays IPv6 and NDP usability status information for single interface
show ipv6 interface [type] [num] prefixDisplays IPv6 network prefix information for single interface
show ipv6 dhcp poolDisplay DHCPv6 configuration pool information
show ipv6 dhcp bindingDisplays all automatic client bindings from the DHCPv6 server binding table
show ipv6 dhcp interface [type] [num]Display DHCPv6 interface information
debug ipv6 ndDebug IPv6 NDP protocol
debug ipv6 dhcpDebug DHCPv6 server
\n

The following console NDP protocol debug log shows that the router received an RS message from host FE80::5850:6D61:1FB:EF3A and responded with an RA message to the multicast address FF02::1 of all nodes in this network:

\n
Router# debug ipv6 nd
ICMP Neighbor Discovery events debugging is on
Router# show logging | include RS
ICMPv6-ND: Received RS on GigabitEthernet0/0/0 from FE80::5850:6D61:1FB:EF3A
Router# show logging | include RA
ICMPv6-ND: Sending solicited RA on GigabitEthernet0/0/0
ICMPv6-ND: Request to send RA for FE80::C801:EFFF:FE5A:8
ICMPv6-ND: Setup RA from FE80::C801:EFFF:FE5A:8 to FF02::1 on GigabitEthernet0/0/0
\n

And the next log shows an example of Stateless DHCPv6 observed after entering the debug ipv6 dhcp debug command. Host FE80::5850:6D61:1FB:EF3A sends an INFORMATION-REQUEST message to the DHCPv6 server, which selects the source address FE80::C801:B9FF:FEF0:8 and sends a response message.

\n
Router#debug ipv6 dhcp
IPv6 DHCP debugging is on

IPv6 DHCP: Received INFORMATION-REQUEST from FE80::5850:6D61:1FB:EF3A on FastEthernet0/0
IPv6 DHCP: Option VENDOR-CLASS(16) is not processed
IPv6 DHCP: Using interface pool LAN_POOL
IPv6 DHCP: Source Address from SAS FE80::C801:B9FF:FEF0:8
IPv6 DHCP: Sending REPLY to FE80::5850:6D61:1FB:EF3A on FastEthernet0/0
\n

The following debug log of Stateful DHCPv6 shows the complete process of two message exchanges (SOLICIT/ADVERTISE, REQUEST/REPLY) on lines 1, 15, 16, and 26.

\n
IPv6 DHCP: Received SOLICIT from FE80::5850:6D61:1FB:EF3A on FastEthernet0/0
IPv6 DHCP: Option UNKNOWN(39) is not processed
IPv6 DHCP: Option VENDOR-CLASS(16) is not processed
IPv6 DHCP: Using interface pool LAN_POOL
IPv6 DHCP: Creating binding for FE80::5850:6D61:1FB:EF3A in pool LAN_POOL
IPv6 DHCP: Binding for IA_NA 0E000C29 not found
IPv6 DHCP: Allocating IA_NA 0E000C29 in binding for FE80::5850:6D61:1FB:EF3A
IPv6 DHCP: Looking up pool 2001:ABCD::/64 entry with username '000100011F3E8772000C29806CCC0E000C29'
IPv6 DHCP: Poolentry for the user not found
IPv6 DHCP: Allocated new address 2001:ABCD::D9F7:61C:D803:DCF1
IPv6 DHCP: Allocating address 2001:ABCD::D9F7:61C:D803:DCF1 in binding for FE80::5850:6D61:1FB:EF3A, IAID 0E000C29
IPv6 DHCP: Updating binding address entry for address 2001:ABCD::D9F7:61C:D803:DCF1
IPv6 DHCP: Setting timer on 2001:ABCD::D9F7:61C:D803:DCF1 for 60 seconds
IPv6 DHCP: Source Address from SAS FE80::C801:B9FF:FEF0:8
IPv6 DHCP: Sending ADVERTISE to FE80::5850:6D61:1FB:EF3A on FastEthernet0/0
IPv6 DHCP: Received REQUEST from FE80::5850:6D61:1FB:EF3A on FastEthernet0/0
IPv6 DHCP: Option UNKNOWN(39) is not processed
IPv6 DHCP: Option VENDOR-CLASS(16) is not processed
IPv6 DHCP: Using interface pool LAN_POOL
IPv6 DHCP: Looking up pool 2001:ABCD::/64 entry with username '000100011F3E8772000C29806CCC0E000C29'
IPv6 DHCP: Poolentry for user found
IPv6 DHCP: Found address 2001:ABCD::D9F7:61C:D803:DCF1 in binding for FE80::5850:6D61:1FB:EF3A, IAID 0E000C29
IPv6 DHCP: Updating binding address entry for address 2001:ABCD::D9F7:61C:D803:DCF1
IPv6 DHCP: Setting timer on 2001:ABCD::D9F7:61C:D803:DCF1 for 172800 seconds
IPv6 DHCP: Source Address from SAS FE80::C801:B9FF:FEF0:8
IPv6 DHCP: Sending REPLY to FE80::5850:6D61:1FB:EF3A on FastEthernet0/0
\n

For complex cases where it is difficult to identify whether the problem is with the host, router, or DHCPv6 server, we recommend using the free open-source network packet analysis software Wireshark to capture packets of the entire process for analysis. While analyzing packets with Wireshark, you can apply the keyword filtering function.

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Filter StringOnly Show
icmpv6.type=133ICMPv6 RS
icmpv6.nd.ra.flagICMPv6 RA
dhcpv6DHCPv6 packets
\n

We can either run Wireshark directly on the host side, or we can use the Switched Port Analyzer (SPAN) provided with the switch. Running on the network side, SPAN can collectively redirect packets from a given port to the monitor port running Wireshark for capturing. Cisco Catalyst 9300 Series switches also directly integrate with Wireshark software to intercept and analyze filtered packets online, making it very easy to use.

\n

Sample packet capture files for three allocation scheme are available here for download and study: slaac.pcapstateless-dhcpv6.pcapstateful-dhcpv6.pcap

\n

References

\n

IPv6 Product Certification Test

\n

Accurate and effective testing of IPv6 products is key to ensuring high interoperability, security, and reliability of IPv6 infrastructure deployments. The IPv6 Ready logo is an IPv6 testing and certification program created by the IPv6 Forum. Its goals are to define IPv6 conformance and interoperability test specifications, provide a self-testing toolset, establish Global IPv6 Test Centers and provide product validation services, and finally, issue IPv6 Ready logo.

\n

In May 2020, IPv6 Ready Logo Program published new version 5.0 test specifications

\n
    \n
  • IPv6 Core Protocols Test Specification (Conformance)
  • \n
  • IPv6 Core Protocols Interoperability Test Specification (Interoperability)
  • \n
\n

Along with these two new test specifications, the project team also affirmed two permanent changes:

\n
    \n
  1. Testing must be done in an IPv6-only environment, without any IPv4 being used for the device to function.
  2. \n
  3. The device under test must have IPv6 on and enabled on all IP interfaces by default.
  4. \n
\n

Not surprisingly, the new version 5.0 core protocols test specification has a section dedicated to defining SLAAC test cases to validate this core IPv6 protocol.

\n

IPv6 Core Protocol RFC List

\n

In the list below, the RFCs shown in bold are directly covered by the IPv6 Ready Version 5.0 Core Protocol Test Specification:

\n
    \n
  • RFC 4191 Default Router Preferences and More-Specific Routes
  • \n
  • RFC 4193 Unique Local IPv6 Unicast Addresses
  • \n
  • RFC 4291 IP Version 6 Addressing Architecture
  • \n
  • RFC 4443 Internet Control Message Protocol (ICMPv6) for the Internet Protocol Version 6 (IPv6) Specification
  • \n
  • RFC 4861 Neighbor Discovery for IP version 6 (IPv6)
  • \n
  • RFC 4862 IPv6 Stateless Address Autoconfiguration
  • \n
  • RFC 4941 Privacy Extensions for Stateless Address Autoconfiguration in IPv6
  • \n
  • RFC 5095 Deprecation of Type 0 Routing Headers in IPv6
  • \n
  • RFC 6724 Default Address Selection for Internet Protocol Version 6 (IPv6)
  • \n
  • RFC 6980 Security Implications of IPv6 Fragmentation with IPv6 Neighbor Discovery
  • \n
  • RFC 7217 A Method for Generating Semantically Opaque Interface Identifiers with IPv6 Stateless Address Autoconfiguration (SLAAC)
  • \n
  • RFC 8064 Recommendation on Stable IPv6 Interface Identifiers
  • \n
  • RFC 8106 IPv6 Router Advertisement Options for DNS Configuration
  • \n
  • RFC 8200 Internet Protocol, Version 6 (IPv6) Specification
  • \n
  • RFC 8201 Path MTU Discovery for IP version 6
  • \n
  • RFC 8415 Dynamic Host Configuration Protocol for IPv6 (DHCPv6)
  • \n
\n","categories":["Study Notes"],"tags":["TCP/IP","Cisco Technology"]},{"title":"Purdue CS24000 Fall 2018 Midterm I Solutions","url":"/en/2024/02/24/Purdue-CS240-2018-Fall-Midterm1/","content":"

Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solution and study notes for the Fall 2018 Midterm 1 exam.

\n

CS24000 Syllabus

\n

Below are extracted from the Spring 2024 CS24000 course syllabus:

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n\n

\n
    \n
  • Reference: Beej’s Guide to C Programming; Brian “Beej” Hall; 2007
  • \n
  • Course Outcomes: A student who successfully fulfills the course requirements will have the ability to:\n
      \n
    • write quality code that is readable, maintainable, and well commented
    • \n
    • create, compile, and execute C programs using industry standard tools including the GNU Compiler Collection
    • \n
    • apply debugging techniques to analyze, identify, and fix errors
    • \n
    • assess and address security-related issues in code bases written in C
    • \n
    • produce code that appropriately and properly utilizes pointers
    • \n
    • solve problems through the application of explicit memory management
    • \n
    • design and implement programs in C that utilize dynamic data structures such as linked lists and trees
    • \n
  • \n
  • Lectures:
  • \n
\n

Fall 2018 Midterm 1 Exam

\n
\n

Exam Solutions and Notes

\n

Problem 1 (20 pts)

\n
    \n
  • (a) gcc -Wall -Werror -g -c abc.c -o xyz.o
    \nExplanation of the options used:

    \n
      \n
    • -Wall: Enable all warnings.
    • \n
    • -Werror: Treat warnings as errors.
    • \n
    • -g: Include debugging information in the output file.
    • \n
    • -c: Compile or assemble the source files, but do not link.
    • \n
    • abc.c: The source file to be compiled.
    • \n
    • -o xyz.o: Specify the output file name (xyz.o).
    • \n
    \n

    📝Notes: This output file xyz.o is not executable since it is just the object file for a single c source file. We need to link to the standard library to make a executable file. If we force to run this xyz.o, it will return something like exec format error.

  • \n
  • (b) gcc xyz.o abc.o def.c -o prog
    \nExplanation:

    \n
      \n
    • xyz.o, abc.o: Object files to be linked.
    • \n
    • def.c: Source file to be compiled and linked.
    • \n
    • -o prog: Specify the output file name (prog).
    • \n
  • \n
  • (c) It advises gcc to include all warnings that help detect potentially problematic code.

  • \n
  • (d) Many functions found in the string library (declared in string.h) rely on null-terminated strings to operate correctly. Null-terminated strings are sequences of characters followed by a null character ('\\0'), which indicates the end of the string. Functions like strlen, strcpy, strcat, strcmp, and others expect null-terminated strings as input and produce null-terminated strings as output.

  • \n
  • (e) In C, memory for a variable is allocated during its definition, not during its declaration.

    \n

    Declaration is announcing the properties of variable (no memory allocation), definition is allocating storages for a variable. Put pure declaration (struct, func prototype, extern) outside of the func, put definition inside func.

  • \n
  • (f) size = 32 (There are 8 integer elements in this array, so 4 * 8.)

  • \n
  • (g) 5 (Because ptr is given the address of the 3rd element. So *(ptr - 1) is the value of the 2nd element.)

  • \n
  • (h) 12 (This is equal to *(ptr - *(ptr + 3)), then *(ptr - 2). So finally it points to the 1st element of the array.)

  • \n
  • (i) 8 (Because it mentions \"64-bit architecture\", so all addresses are of size 64-bit)

  • \n
\n

Problem 2 (20 pts)

\n
    \n
  • (a) The answer is shown below: (remember to define ID_LEN first and add ';' after the '}')

    \n

    #define ID_LEN (5)

    struct resistor {
    char id[ID_LEN];
    float max_power;
    int resistance;
    };

  • \n
  • (b) The answer is shown below:

    \n

    typedef struct resistor resistor_t;

  • \n
  • (c) The answer is shown below: (remember to define ID_LEN first and add ';' after the '}')

    \n

    #define CNAME_LEN (24)

    struct circuit_struct {
    char name[CNAME_LEN];
    resistor_t resistors[10];
    };

  • \n
  • (d) It will print sizeof = 920. Explanation: 5 * (24 + 10 * (8 + 4 + 4)) = 920. This is because the id inside the resistor will occupy 8 bytes after padding to a multiple of 4.

    \n

    struct circuit_struct circuit_board[5];

  • \n
  • (e) The function can be written like the following:

    \n

    int find_voltage(resistor_t r, int c) {
    return (c * r.resistance);
    }

  • \n
\n

Problem 3 (40 pts)

\n

The complete program is shown below

\n
#include <stdio.h>

#define ID_LEN 5
#define CNAME_LEN 24

struct resistor {
char id[ID_LEN];
float max_power;
int resistance;
};

typedef struct resistor resistor_t;

struct circuit_struct {
char name[CNAME_LEN];
resistor_t resistors[10];
};

int blown_resistors(char* infile, char* outfile, float voltage) {

FILE *in = fopen(infile, "r");
if(!in) return -1;

FILE *out = fopen(outfile, "w");
if(!out) {
fclose(in);
in = NULL;
return -1;
}

// First pass - calculate total resistance
int total_resistance = 0;
int items;
char id[ID_LEN];
int resistance;
float max_power;

fseek(in, 0, SEEK_SET);

while (fscanf(in, "%[^,],%d,%f\\n", id, &resistance, &max_power) == 3) {
total_resistance += resistance;
}

if (!feof(in)) {
// Input format error
fclose(in);
in = NULL;
fclose(out);
out = NULL;
return -1;
}

// Calculate current
float current = voltage / total_resistance;

// Second pass - check for blown resistors
int blown_count = 0;

fseek(in, 0, SEEK_SET);

while (fscanf(in, "%[^,],%d,%f\\n", id, &resistance, &max_power) == 3) {
float power = current * current * resistance;
if (power > max_power) {
blown_count++;
fprintf(out, "%s, %.2f\\n", id, power);
}
}

fclose(in);
in = NULL;
fclose(out);
out = NULL;

return blown_count;
}

int main(void) {
printf("return is %d\\n", blown_resistors("input", "output", 100));
return 0;
}
\n

Problem 4 (20 pts)

\n

The solution can be like this: (the include and struct definition are not necessary)

\n
#include <stdio.h>

struct coord {
float x;
float y;
};

struct coord find_center(FILE *file_ptr) {
struct coord center = {0.0, 0.0};
struct coord temp;
int count = 0;

if (file_ptr == NULL) {
fprintf(stderr, "Error: NULL file pointer\\n");
return center;
}

// Set file position indicator to the beginning
rewind(file_ptr);

while (fread(&temp, sizeof(struct coord), 1, file_ptr) == 1) {
center.x += temp.x;
center.y += temp.y;
count++;
}

if (count > 0) {
center.x /= count;
center.y /= count;
}

return center;
}
\n","categories":["Study Notes"],"tags":["C/C++ Programming"]},{"title":"Purdue CS24000 2022 and 2023 Summer Midterm Exam Solutions","url":"/en/2024/02/25/Purdue-CS240-2022-2023-Summer-Midterm/","content":"

Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solutions and study notes for the 2022 and 2023 Midterm exams.

\n

Introduction

\n

Below are extracted from the Summer 2023 CS24000 course homepage:

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n\n

\n
    \n
  • Reference: Beej’s Guide to C Programming; Brian “Beej” Hall; 2007
  • \n
  • Lecture Subjects\n
      \n
    • Roles of C compiler, C preprocessor, linker, loader.
    • \n
    • Main memory: addresses and their content, meaning of variables.
    • \n
    • Reading from stdin and writing to stdout.
    • \n
    • Fundamental difference between printf() and scanf(): need to pass addresses in scanf().
    • \n
    • Pointers and indirection.
    • \n
    • Global vs. local variables.
    • \n
    • Function calls and passing arguments.
    • \n
    • Passing by value vs. reference, their typical usage.
    • \n
    • Basic methods for run-time debugging.
    • \n
    • Memory layout of 1-D arrays, indexing using pointer notation.
    • \n
    • Segmentation fault, silent run-time errors.
    • \n
    • Array overrun, stack smashing and gcc intervention.
    • \n
    • Scope of global and local variables, properties of static variables.
    • \n
    • Memory layout of 2-D integer arrays, indexing using pointer notation.
    • \n
    • Basic string processing.
    • \n
    • Function pointers.
    • \n
    • Basic file I/O.
    • \n
    • Controlling the number of bytes read to prevent stack smashing.
    • \n
    • Using the make tool to help automate code maintenance.
    • \n
    • Bit processing techniques, common applications.
    • \n
    • Basic dynamic memory allocation using malloc(), 1-D and 2-D array examples.
    • \n
    • Applications of 2-D tables, limitation and caution regarding the use of variable length arrays.
    • \n
    • Command-line argument support in main(), loader invocation and passing arguments using execl().
    • \n
    • Applications of command-line arguments.
    • \n
    • Composite data types using struct, its memory structure, and applications.
    • \n
    • Conversion/casting of data types.
    • \n
    • Variadic functions: structure and applications.
    • \n
    • Application of passing function pointers: responding to events via callback functions (i.e., throwing and catching exceptions).
    • \n
    • union and enum: structure and applications.
    • \n
    • Role of const qualifier in argument passing.
    • \n
    • Basic structure of concurrent client/server apps, shell as an example app.
    • \n
    • Additional features and applications of file I/O.
    • \n
  • \n
\n

Summer 2022 Midterm Solutions and Notes

\n

Problem 1 (36 pts)

\n

(a) Consider the code snippet

\n
int a, *b, *c;
a = 3; b = &a;
printf("%d", *b);
*c = 5;
printf("%d", *c);
\n

Explain in detail what is likely to happen if the code snippet is compiled and executed.

\n

(b) What are the possible outcomes if the code snippet

\n
char r[4];
r[0] = 'H';
r[1] = 'i';
printf("%s", r);
\n

is compiled and executed? Explain your reasoning.

\n

(c) Suppose we have a 2-D array, int x[2][3], wherein 6 integers are stored. What array expression is *(*(x+1)+2) equivalent to, and why?

\n

Problem 1 Solution

\n
\n

(a) The first printf() outputs 3 since b is a pointer to integer variable a. *c = 5 is likely to generate a segmentation fault since the code does not place a valid address in c before this assignment. The second printf() is likely not reached due to a segmentation fault from *c = 5 which terminates the running program.

\n

(b) There are two possible outcomes:

\n
    \n
  1. prints \"Hi\" to stdout.
  2. \n
  3. prints \"Hi\" followed by additional byte values.
  4. \n
\n

Explanation: If the memory location r[2] contains EOS ('\\0') then the first outcome results. Otherwise, printf() will continue to print byte values (not necessarily ASCII) until a byte containing 0 (i.e.,EOS) is reached.

\n

(c) Equivalent to x[1][2].

\n

Explanation: In our logical view of 2-D arrays: x points to the location in memory where the beginning addresses of two 1-D integer arrays are located. Therefore x+1 points to the beginning address of the second 1-D integer array. *(x+1) follows the pointer to the beginning address of the second 1-D integer array. *(x+1)+2 results in the address at which the third element of the second 1-D integer array is stored. *(*(x+1)+2) accesses the content of the third element of the second 1-D integer array. Hence equivalent to x[1][2].

\n\n
\n

Problem 2 (32 pts)

\n

(a) Suppose main() calls function

\n
int abc(void) {
int a = 3, static int b = 1;
if(++a > ++b) return a++;
else return ++b;
}
\n

three times. Explain what values are returned to main() in each of the three calls to abc().

\n

(b) Suppose the code snippet

\n
float m, **n;
m = 3.3;
printf("%f", m);
**n = 5.5;
printf("%p", n);
\n

is compiled and executed. What is likely to happen, and why? How would you modify the code (involving printf() calls) to facilitate ease of run-time debugging?

\n

Problem 2 Solution

\n
\n

(a) Here are the three return values for each call and the explanation:

\n
    \n
  1. First call returns 4. The if-statement checks 4 > 2 and a++ returns 4 before incrementing a.
  2. \n
  3. Second call returns 4. Before the if-statement, the static variable b becomes 2 since it preserves the previous value from the first call. So the if-statement checks 4 > 3. Hence a++ returns 4.
  4. \n
  5. Third call return 5. Now the static variable b becomes 3 at the beginning of the call, and the if-statement checks 4 > 4. So the program goes to the else-part which increments b again and returns b. Hence the function call returns 5.
  6. \n
\n

(b) Since we did not assign a valid address to n, **n is likely to reference an invalid address that triggers a segmentation fault which terminates the running program.

\n

Although the first printf() call was successful, 3.3 will likely will not be output to stdout (i.e., display) due to abnormal termination of the program and buffering by stdio library functions.

\n

Adding a newline in the first printf() call, or calling fflush(stdout) after the first printf() call will force 3.3 in the stdout buffer to be flushed before the program terminates due to segmentation fault.

\n\n
\n

Problem 3 (32 pts)

\n

(a) Suppose you are supervising a team of C programmers. One of the team members is responsible for coding a function, int readpasswd(void), that reads from stdin a new password and checks that it contains upper case letters, special characters, etc. per company policy. The team member shows you part of the code

\n
int readpasswd() {
char secret[100];
scanf("%s", secret);
/* code follows to check validity of password */
}
\n

that reads a password from stdin and stores it in local variable secret for further processing. Explain why you would be alarmed by the code. How would you rewrite to fix the problem in the code?

\n

(b) Code main() that reads a file, test.out, byte by byte using fgetc() and counts how many bytes are ASCII characters. main() outputs the count to stdout. Focus on making sure that your code is robust and does not crash unexpectedly.

\n

Problem 3 Solution

\n
\n

(a) The scanf() does not prevent user input that exceeds buffer size (100 characters) from overwriting memory in readpasswd()'s stack frame, potentially modifying its return address. This can lead to the execution of unintended code such as malware.

\n

Alternate: The scanf() functions can lead to a buffer overflow if used improperly. Here in this function, it does not have bound checking capability and if the input string is longer than 100 characters, then the input will overflow into the adjoining memory and corrupt the stack frame.

\n

📝Notes: This is a major security flaw in scanf family (scanf, sscanf, fscanf ..etc) esp when reading a string because they don't take the length of the buffer (into which they are reading) into account.

\n

To fix this, the code should explicitly check that no more than 100 characters are read from stdin to prevent overflow over secret[100]. This can be done by reading character by character using getchar() in a loop until a newline is encountered or 100 characters have been read.

\n

(b) A sample solution can be seen below

\n
#include <stdio.h>
int main() {
FILE *fp;
int c, count;

if ((fp = fopen("test.out","r")) == NULL) {
fprintf(stderr,"opening file blog.dat failed\\n");
exit(1);
}

count = 0;
while ((c = fgetc(fp)) != EOF) {
if (0 <= c <= 127) {
// it's an ASCII character, increment count
count++;
}
}
printf("count = %d\\n", count); //output result fclose(fp);
fp = NULL;
}
\n\n
\n

Bonus Problem (10 pts)

\n

Suppose you are given the code in main.c

\n
int s[5]; 
int main() {
int i;
for (i=0; i<50; i++)
s[i] = 0;
}
\n

which is compiled using gcc and executed. What are the two possible outcomes? Explain your answer.

\n

Bonus Problem Solution

\n
\n
    \n
  • Outcome 1: The for-loop overwrites global memory following s[5] which may, or may not, corrupt program data and computation but does not crash the running program (i.e., silent run-time bug).
  • \n
  • Outcome 2: The for-loop overwrites global memory following s[5] which exceeds the running program's valid memory, resulting in a segmentation fault.
  • \n
\n\n
\n

Summer 2023 Midterm Solutions and Notes

\n

Problem 1 (30 pts)

\n

(a) Consider the code snippet

\n
int x, *y, *z;
x = 5;
y = &x;
*y = 10;
printf("%d %p\\n", x, y);
*z = 3;
\n

Explain what is likely to happen if the code snippet is compiled and executed as part of main().

\n

(b) Explain what the declarations of g and h mean:

\n
char *g(char *), (*h)(char *);
\n

For the two assignment statements to be meaningful

\n
x = g(s);
h = y;
\n

what must be the types of x and y? Provide the C statements for their type declarations.

\n

Problem 1 Solution

\n
\n

(a) printf() will output 10 (for x) and the address of x (in hexadecimal notation) which is contained in y. Assignment statement *z = 3 will likely trigger a segmentation fault since a valid address has not been stored in z.

\n

(b) g is a function that takes a single argument that is a pointer to char (i.e., char *), and g returns a pointer to char (i.e., address that points to char). h is a function pointer that takes a single argument that is a pointer to char, and h returns a value of type char.

\n

x is a pointer to char, i.e., char *x. y is a function that takes an argument that is a pointer to char and returns a value of type char, i.e., char y(char *).

\n\n
\n

Problem 2 (30 pts)

\n

(a) For the function

\n
void fun(float a) {
float x[5], i;
for (i=0; i<8; i++)
x[i] = a;
}
\n

explain what is likely to happen if fun() is called by main(). Explain how things change if 1-D array x is made to be global.

\n

(b) What are potential issues associated with code snippet

\n
FILE *f;
char r[100];
f = fopen("data.dat", "r");
fscanf(f, "%s", r);
\n

Provide modified code that fixes the issues.

\n

Problem 2 Solution

\n
\n

(a) Calling fun() will likely generate a stack smashing error. This is so since x is local to fun() and overflowing the 1-D array (by 3 elements, i.e., 12 bytes) is likely to cause the canary (bit pattern) inserted by gcc (to guard the return address) to be changed. If x is made global, gcc does not insert a canary, hence stack smashing will not occur. However, overflowing x may, or may not, trigger a segmentation fault.

\n

(b) Two potential issues:

\n
    \n
  1. fopen() may fail and return NULL.
  2. \n
  3. fscanf() may overflow 1-D array r if the character sequence in data.dat exceeds 100 bytes.
  4. \n
\n

To fix these, do the following modifications:

\n
f = fopen("data.dat", "r");
if (f == NULL) {
printf("error opening data.dat");
exit(1);
}
fscanf(f, "%99s", r);
\n\n
\n

Problem 3 (40 pts)

\n

(a) A 2-D integer array, int d[100][200], declaration is restrictive in that it hardcodes the number of rows and columns to fixed values 100 and 200, respectively. Suppose two integers N and M are read from stdin that specify the number of rows and columns of a 2-D integer array which is then used to read N x M integers from stdin into main memory. Provide C code main() that uses malloc() to achieve this task. Your code should be complete but for including header files.

\n

(b) Provide code that reads a value of type unsigned int from stdin, then uses bit processing techniques to count how many of the 32 bits contain bit value 0. Annotate your code to note what the different parts are doing.

\n

Problem 3 Solution

\n
\n

(a) The complete code is shown below (Note we skip the NULL check for the return of malloc(), add that after each such call if required)

\n
int main() {
int **d;
int N, M, i, j;

scanf("%d %d", &N, &M);
d = (int **)malloc(N * sizeof(int *));

for(i=0; i<N; i++) {
// can also use d[i] on the left below
*(d + i) = (int *)malloc(M * sizeof(int));
}
for (i=0; i<N; i++) {
for (j=0; j<M; j++) {
scanf("%d", &d[i][j]);
}
}
}
\n

📝Notes: Freeing memory of such a 2-D integer array also needs two steps:

\n
void free_2d_array(int **array, int rows) {
for (int i = 0; i < rows; i++) {
// equivalent to free(array[i])
free(*(array+i));
}
free(array);
}
\n

(b) The solution code can be seen below

\n
unsigned int x, m = 1;
int i, count = 0;

scanf("%u", &x);

for (i=0; i<32; i++) {
if ((x & m) == 0) {
count++;
}
x = x >> 1;
}

printf("%d", count);
\n\n
\n

Bonus Problem (10 pts)

\n

Explain why printf(\"%d\", x) passes argument x by value whereas scanf(\"%d\", &x) passes the argument by reference. Can one code printf() so that it passes x by reference? If so, why is it not done?

\n

Bonus Problem Solution

\n
\n

printf() only needs a copy of the value of x to do its work of printing the value to stdout. scanf() needs the address of x so that the value entered through stdin (by default, keyboard) can be stored at the address of x. Yes, since following the address of x allows printf() to access its value. It is not necessary to reveal the address of x to printf() since it only requires its value.

\n\n
\n","categories":["Study Notes"],"tags":["C/C++ Programming"]},{"title":"Purdue CS24000 2022 Summer Final Exam Solutions","url":"/en/2024/03/24/Purdue-CS240-2022-Summer-Final/","content":"

Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solutions and study notes for the 2022 and 2023 Final exams.

\n

Introduction

\n

Below are extracted from the Summer 2023 CS24000 course homepage:

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n\n

\n\n

Summer 2022 Final Solutions and Notes

\n

Problem 1 (45 pts)

\n

(a) Which statements in the code

\n
typedef struct friend {
char *nickname;
unsigned int year;
} friend_t;

int main() {
friend t *amigo;
amigo->year = 2017;
strcpy(amigo->nickname, "fish");
}
\n

are problematic, likely to trigger segmentation fault? Augment the code by adding calls to malloc() so that the bugs are fixed.

\n

(b) Explain the difference between fun1 and fun2 which are declared as char *fun1(char *) and char (*fun2)(char *), respectively. Code a function fun3 that takes a string as argument and returns the last character of the string. You may assume that the string is of length at least 1 (not counting EOS).

\n

(c) Suppose a user enters the command, %/bin/cp file1 file2, using a shell to copy the content of file1 to file2 on one of our lab machines. From the viewpoint of the shell, from where does it read its input /bin/cp file1 file2? From the viewpoint of the app /bin/cp which is coded in C, how does it access its input which specify the names of two files whose content is to be copied? Before calling execv() what must the shell do to prepare the arguments of execv() so that /bin/cp has access to the two file names?

\n

Problem 1 Solution

\n
\n

(a) Problematic:

\n
amigo->year = 2017;
strcpy(amigo->nickname, "fish");
\n

The reason is that the pointer amigo has not been initialized to the address of any allocated memory space yet.

\n

Agumentation:

\n
amigo = (friend_t *)malloc(sizeof(friend_t));
amigo->nickname = (char *)malloc(5);
...
\n

(b) fun1 takes as argument a pointer to char and returns a pointer to char. fun2 is a function pointer to a function that takes as argument a pointer to char and returns a value of type char.

\n
char fun3(char *s) {    
while (*s != '\\0')
s++;
return *(s-1);}
\n

(c) Input /bin/cp file1 file2 is read from stdin.

\n

main(int argc, char *argv) of /bin/cp accesses the two file names via argv[1] and argv[2].

\n

Assuming a variable s is of type, char **s, a shell must allocate sufficient memory for s and copy /bin/cp into s[0]1, file1 into s[1], file2 into s[2], and set s[3] to NULL.

\n\n
\n

Problem 2 (30 pts)

\n

(a) Code a function, unsigned int countdbl(long), that takes a number of type long as input, counts the number of 0s in the bit representation of the input, and returns 0 if the count is an even number, 1 if odd. Use bit processing techniques to solve the problem. (b) gcc on our lab machine, by default, will insert code to detect stack smashing at run-time. What does gcc's code try to prevent from happening? In the case of reading input from stdin (or file), what is a common scenario and programming mistake that can lead to stack smashing? Provide an example using scanf() (or fscanf()). What issound programming practice that prevents stack smashing?

\n

Problem 2 Solution

\n
\n

(a)

\n
unsigned int countdbl(long x) {
int i;
unsigned int count = 0;
long m = 1;

for(i=0; i<64; i++) {
// Check all bits of long value from lsb to msb.
if ((x & m) == 0) count++;
x = x >> 1;
}

if ((count & 1) == 0) return 0; // Check if count is even.
else return 1; // count is odd
}
\n

(b) When a function is called by another function, gcc tries to detect if the return address has been corrupted and, if so, terminate the running program.This is to prevent the code from jumping to unintended code such as malware.A local variable of a function declared as a 1-D array overflows by input whose length is not checked when reading from stdin (or file).Example: a function contains code

\n
char buf[100];
scanf("%s", buf);
\n

which may overflow buf[] since scanf() does not check for length of the input.Sound practice: use functions to read from stdin (or file) that check for length.In the above example use fgets() instead of scanf().

\n\n
\n

Problem 3 (25 pts)

\n

Code a function that takes variable number of arguments, double multnums(char *, ...), multiplies them and returns the result as a value of type double. The fixed argument is a string that specifies how many arguments follow and their type (integer 'd' or float 'f'). For example, in the call multnums(\"dffd\", 3, 88.2, -100.5, 44), the format string \"dffd\" specifies that four arguments follow where the first character 'd' means the first argument in the variable argument list is of type integer, the second and third 'f' of type float, and the fourth 'd' of type integer. Forgo checking for errors and ignore header files. What would happen in your code if multnums is called as multnums(\"dffd\", 3, 88.2, -100.5, 44, -92, 65)? What about multnums(\"dffd\", 3, 88.2, -100.5)? Explain your reasoning.

\n

Problem 3 Solution

\n
\n
double multnums(char *a, ...) {
int x;
double y, val = 1;
va_list arglist;
\t
va_start(arglist, a);
while (*a != '\\0') {
// Check the format string, character by character until EOS.
if (*a == 'd') { // Interpret argument as int.
x = va_arg(arglist, int);
val = val * x;\t\t
}
else {
// Assumes must be 'f' since forgoing error checking.
// Interpret argument as double (not float).
y = va_arg(arglist, double);
val = val * y;
}
a++;\t
}\t
va_end(arglist);
return val;
}
\n

When a C function is defined with a variable number of arguments, it typically uses the va_arg, va_start, and va_end macros from the <stdarg.h> header to handle the variable arguments.

\n

If the input argument count does not match the format string provided to functions like printf or scanf, it can lead to undefined behavior and potentially cause crashes, memory corruption, or incorrect output/input.

\n

Here are some specific scenarios that can occur when there is a mismatch between the input arguments and the format string:

\n
    \n
  • Too few arguments:\n
      \n
    • If there are fewer arguments than the number of format specifiers in the format string, the behavior is undefined.
    • \n
    • The function may attempt to read from uninitialized memory locations or use garbage values, leading to incorrect results or crashes.
    • \n
  • \n
  • Too many arguments:\n
      \n
    • If there are more arguments than the number of format specifiers in the format string, the extra arguments will be ignored by the function.
    • \n
    • However, if the extra arguments are of a different type than expected, it can lead to incorrect interpretation of the data on the stack, potentially causing crashes or memory corruption.
    • \n
  • \n
\n\n
\n

Bonus Problem (10 pts)

\n

Suppose an ASCII file contains lines where each line is a sequence of characters ending with \\n but for the last line which ends because the end of file is reached. The goal of main() is to read and store the lines of the ASCII into a variable, char **x, where malloc() is used to allocate just enough memory to store the content of the  file. Using only basic file I/O operations discussed in class, describe in words how your code would work to accomplish this task. Be detailed in how the arguments of malloc() are determined to store the file content in x.

\n

Bonus Problem Solution

\n
\n
    \n
  1. Open file, read byte by byte until EOF is reached while counting occurrences of '' to determine the total number of lines (count plus 1). Denote this number of r.Close file.
  2. \n
  3. Use malloc() to allocate 1-D array, int *M, of size r of type int. Open file, read byte by byte, counting for each line the number of bytes. Store the line lengthin 1-D array M. Close file.
  4. \n
  5. Using 1-D array M call malloc() for each line to allocate memory to store the bytes of each line. Point x to the 1-D array of pointers to char.
  6. \n
  7. Open file. Read byte by byte the content of each line into 1-D array of pointersto char pointed to by x.
  8. \n
\n

A sample implementation (not required for this exam) is shown as below:

\n
#include <stdio.h>
#include <stdlib.h>

char **read_file(const char *filename, int *r) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
fprintf(stderr, "Error opening file: %s\\n", filename);
return NULL;
}

// 1. Count the number of lines
int c, line_count = 0;
while ((c = fgetc(file)) != EOF) {
if (c == '\\n')
line_count++;
}
line_count++; // Account for the last line without newline character
*r = line_count;

rewind(file); // Reset the file pointer to the beginning

// 2. Allocate memory for the line lengths array and store the line lengths
int *M = (int *)malloc((*r) * sizeof(int));
if (M == NULL) {
fprintf(stderr, "Error allocating memory\\n");
fclose(file);
return NULL;
}

int i = 0, length = 0;
while ((c = fgetc(file)) != EOF) {
if (c == '\\n') {
M[i++] = length;
length = 0;
} else {
length++;
}
}
M[i] = length; // Store the length of the last line

rewind(file); // Reset the file pointer to the beginning

// 3. Allocate memory for the array of character pointers
char **x = (char **)malloc((*r + 1) * sizeof(char *));
if (x == NULL) {
fprintf(stderr, "Error allocating memory\\n");
free(M);
fclose(file);
return NULL;
}

// 4. Allocate memory for each line and read the file content
for (i = 0; i < *r; i++) {
x[i] = (char *)malloc((M[i] + 1) * sizeof(char));
if (x[i] == NULL) {
fprintf(stderr, "Error allocating memory\\n");
for (int j = 0; j < i; j++)
free(x[j]);
free(x);
free(M);
fclose(file);
return NULL;
}

int j = 0;
while (j < M[i]) {
x[i][j++] = fgetc(file);
}
x[i][j] = '\\0'; // Null-terminate the line
}
x[i] = NULL; // Terminate the array of character pointers

free(M);
fclose(file);
return x;
}
\n\n
\n","categories":["Study Notes"],"tags":["C/C++ Programming"]},{"title":"Purdue CS24000 Fall 2018 Midterm II Solutions","url":"/en/2024/03/27/Purdue-CS240-2018-Fall-Midterm2/","content":"

Purdue University CS24000 is an undergraduate-level course that teaches students programming principles and techniques for problem-solving in the C programming language. Here are the solution and study notes for the Fall 2018 Midterm 2 exam.

\n

CS24000 Syllabus

\n

Below are extracted from the Spring 2024 CS24000 course syllabus:

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n\n

\n
    \n
  • Reference: Beej’s Guide to C Programming; Brian “Beej” Hall; 2007
  • \n
  • Course Outcomes: A student who successfully fulfills the course requirements will have the ability to:\n
      \n
    • write quality code that is readable, maintainable, and well commented
    • \n
    • create, compile, and execute C programs using industry standard tools including the GNU Compiler Collection
    • \n
    • apply debugging techniques to analyze, identify, and fix errors
    • \n
    • assess and address security-related issues in code bases written in C
    • \n
    • produce code that appropriately and properly utilizes pointers
    • \n
    • solve problems through the application of explicit memory management
    • \n
    • design and implement programs in C that utilize dynamic data structures such as linked lists and trees
    • \n
  • \n
  • Lectures:
  • \n
\n

Fall 2018 Midterm 2 Exam

\n
\n

Exam Solutions and Notes

\n

Problem 1 (30 pts)

\n

(a) Code without using array brackets:

\n
int reverse(int *source, int *dest, int n) {
int sum = 0;
int* srcptr = source;
int* dstptr = dest;

for (int i = 0; i < n; i++) {
*(dstptr + i) = *(srcptr + n - 1 - i);
sum += *(dstptr + i);
}
return sum;
}}
\n

In summary, the reverse function reverses the order of elements in the source array, stores them in the dest array, and calculates the sum of the reversed elements.

\n

(b) The atomic weight of Aluminum is 26.981.

\n

(c) Structure for a singly-linked list node containing an integer:

\n
typedef struct single_node {
int data;
struct single_node *next;
} single_node_t;
\n

(d) Function to prepend a node to a singly-linked list:

\n
void push(single_node_t **head, single_node_t *node) {
assert(head != NULL);
assert(node != NULL);
assert(node->next == NULL);
node->next = *head;
*head = node;
return;
}
\n

(e) Function to remove the first node from a singly-linked list:

\n
single_node_t *pop(single_node_t **head) {
assert(head != NULL);
if (*head == NULL) {
return NULL;
}

single_node_t *tmp = *head;
*head = (*head)->next;
return tmp;
}
\n

Problem 2 (40 pts)

\n

(a) Structure for a doubly-linked list node containing a string and an integer:

\n
typedef struct double_node {
char *name;
int age;
struct double_node *prev;
struct double_node *next;
} double_node_t;
\n

(b) Function to create a new doubly-linked list node:

\n
double_node_t *create(char *name, int age) {
double_node_t *new_node = malloc(sizeof(double_node_t));
if (new_node == NULL) {
// Handle memory allocation failure
return NULL;
}

unsigned_int name_len = strlen(name) + 1;
new_node->name = malloc(name_len * sizeof(char));
if (new_node->name == NULL) {
// Handle memory allocation failure
free(new_node);
return NULL;
}

strcpy(new_node->name, name);
new_node->age = age;
new_node->prev = NULL;
new_node->next = NULL;
return new_node;
}
\n

(c) Function to delete a node from a doubly-linked list:

\n
void delete(double_node_t *node) {
if (node == NULL) {
return;
}

if (node->prev) {
node->prev->next = node->next;
}

if (node->next) {
node->next->prev = node->prev;
}

free(node->name);
free(node);
}
\n

(d) Function to insert a new node after a given node in a doubly-linked list:

\n
void insert(double_node_t *node, double_node_t *new_node) {
if (node == NULL || new_node == NULL) {
return;
}

new_node->prev = node;
new_node->next = node->next;

if (node->next) {
node->next->prev = new_node;
}

node->next = new_node;
}
\n

Problem 3 (30 pts)

\n

(a) Structure for a binary tree node:

\n
typedef struct tree_node {
int value;
bool invalid;
struct tree_node *left;
struct tree_node *right;
} tree_node_t;
\n

(b) The size of the tree_node_t structure on a 64-bit architecture system is 24 bytes (4 bytes for int, 1 byte for bool, and 8 bytes for each pointer).

\n

(c) Function to mark a node as invalid:

\n
void delete_node(tree_node_t *node) {
if (node) {
node->invalid = true;
}
return;
}
\n

(d) Function to remove a node from a binary tree (assuming it's not the root):

\n
void free_node(tree_node_t *node) {
if (node) {
tree_node_t *parent = get_parent(node);

// Case 1: Node has no children
if (!node->left && !node->right) {
if (parent->left == node) {
parent->left = NULL;
} else {
parent->right = NULL;
}
}
// Case 2: Node has only one child
else if (!node->left || !node->right) {
tree_node_t *child = node->left ? node->left : node->right;
if (parent->left == node) {
parent->left = child;
} else {
parent->right = child;
}
}
// Case 3: Node has two children
else {
// Find the right most child of the left child
tree_node_t *predecessor = node->left;
while (predecessor->right) {
predecessor = predecessor->right;
}

// Adjust the predecessor and its parent's children links
tree_node_t *predecessor_parent = get_parent(predecessor);
if (predecessor_parent != node) {
predecessor_parent->right = predecessor->left;
predecessor->left = node->left;
}
predecessor->right = node->right;

// Promote it as the new child of the removed node's parent
if (parent->left == node) {
parent->left = predecessor;
} else {
parent->right = predecessor;
}
}

free(node);
}
}
\n

(e) Recursive function to delete invalid nodes from a binary tree:

\n
int flush_tree(tree_node_t *root, void (*my_del)(tree_node_t *)) {
if (root == NULL) {
return 0;
}

int deleted = 0;

// recursively traverse the tree in postfix (L-R-N) fashion
deleted += flush_tree(root->left, my_del);
deleted += flush_tree(root->right, my_del);

if (root->invalid) {
my_del(root);
deleted++;
}

return deleted;
}
\n","categories":["Study Notes"],"tags":["C/C++ Programming"]},{"title":"Purdue MA 26500 Fall 2022 Midterm I Solutions","url":"/en/2024/01/19/Purdue-MA265-2022-Fall-Midterm1/","content":"

Linear algebra provides mathematical tools to represent and analyze data and models in higher dimensions. It is essential for machine learning, computer graphics, control theory, and other scientific and engineering fields. Starting from this post, I will provide study guides and solutions to Purdue MA26500 exams in the last few semesters.

\n

You can’t learn too much linear algebra
Benedict Gross (American mathematician, professor at the University of California San Diego and Harvard University, member of the National Academy of Sciences)

\n
\n

Introduction

\n

Purdue University is a world-renowned public research university that advances scientific, technological, engineering, and math discoveries. Purdue Department of Mathematics provides a linear algebra course MA 26500 every semester, as it is mandatory for undergraduate students of many science and engineering majors.

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

Basic Information

\n
    \n
  • Course Title: Introduction to Linear Algebra
  • \n
  • Credit Hours: 3.00
  • \n
  • Lectures: 50 minutes per session, 3 times a week, 16 weeks
  • \n
  • Course Description: A computational introduction to linear algebra, which plays a fundamental role in science, engineering, and the social sciences, and this course will provide the student a firm basis for the use of such.
  • \n
  • Key Topics: systems of linear equations; matrix algebra; vector spaces; determinants; eigenvalues and eigenvectors; diagonalization of matrices; and applications
  • \n
  • Textbook: Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald
  • \n
  • Study Guide: Study Guide for Linear Algebra and Its Applications 6th Edition by the same authors for the students.
  • \n
\n
\n

Homework and Exams

\n
    \n
  • 35 online homework assignments using MyLab Math
  • \n
  • 36 handwriting homework assignments (Spring 2024)
  • \n
  • Midterm I (Book Sections 1.1 – 3.3): 1 hour (6-week mark)
  • \n
  • Midterm II (Book Sections 4.1 – 5.7): 1 hour (12-week mark)
  • \n
  • Midterm format: a combination of multiple-choice questions and short answer questions
  • \n
  • Final (Comprehensive Common): 2 hours (16-week mark), all multiple-choice questions
  • \n
  • Grades\n
      \n
    • Online Homework - 17%
    • \n
    • Written Homework - 8%
    • \n
    • Midterm Exam I - 20%
    • \n
    • Midterm Exam II - 20%
    • \n
    • Final Exam - 35%
    • \n
  • \n
\n

Reference Links

\n\n

Fall 2022 Midterm I Solutions

\n

Problem 1 (10 points)

\n

Let \\(A=\\begin{bmatrix}1 & 2\\\\3 & 5\\\\\\end{bmatrix}\\)\\(B=\\begin{bmatrix}3 & 1\\\\4 & 1\\\\\\end{bmatrix}\\), and \\(C=AB^{-1}= \\begin{bmatrix}a & b\\\\c & d\\\\\\end{bmatrix}\\), then \\(a+b+c+d=\\)

\n
    \n
  • A. \\(-7\\)
  • \n
  • B. \\(8\\)
  • \n
  • C. \\(7\\)
  • \n
  • D. \\(-8\\)
  • \n
  • E. \\(0\\)
  • \n
\n

Problem 1 Solution

\n
\n

Because \\(C=AB^{-1}\\), we can multiple both sides by \\(B\\) and obtain \\(CB=AB^{-1}B=A\\). So \\[\n\\begin{bmatrix}a & b\\\\c & d\\\\\\end{bmatrix}\n\\begin{bmatrix}3 & 1\\\\4 & 1\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 2\\\\3 & 5\\\\\\end{bmatrix}\n\\] Further, compute at the left side \\[\n\\begin{bmatrix}3a+4b & a+b\\\\3c+4d & c+d\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 2\\\\3 & 5\\\\\\end{bmatrix}\n\\] From here we can directly see \\(a+b=2\\) and \\(c+d=5\\), so \\(a+b+c+d=7\\). The answer is C.

\n

⚠️Alert: There is no need to find the inverse of the matrix \\(B\\) and multiply the result with \\(A\\). Even if you can deduce the same answer, it is very inefficient and takes too much time.

\n\n
\n

Problem 2 (10 points)

\n

Let \\(\\mathrm L\\) be a linear transformation from \\(\\mathbb R^3\\) to \\(\\mathbb R^3\\) whose standard matrix is \\(\\begin{bmatrix}1 &2 &3\\\\0 &1 &1\\\\2 &3 & k\\\\\\end{bmatrix}\\) where \\(k\\) is a real number. Find all values of \\(k\\) such that \\(\\mathrm L\\) is one-to-one.

\n
    \n
  • A. \\(k\\neq 1\\)
  • \n
  • B. \\(k\\neq 2\\)
  • \n
  • C. \\(k\\neq 3\\)
  • \n
  • D. \\(k\\neq 4\\)
  • \n
  • E. \\(k\\neq 5\\)
  • \n
\n

Problem 2 Solution

\n
\n

For this standard matrix, do elementary row operations below to achieve row echelon form.

\n

First, add -2 times row 1 to row 3: \\[\\begin{bmatrix}1 &2 &3\\\\0 &1 &1\\\\0 &-1 &k-6\\\\\\end{bmatrix}\\] Then add row 2 to row 3: \\[\\begin{bmatrix}1 &2 &3\\\\0 &1 &1\\\\0 &0 &k-5\\\\\\end{bmatrix}\\] If \\(k=5\\), the equation \\(A\\mathbf x=\\mathbf b\\) has a free variable \\(x_3\\) and each \\(\\mathbf b\\) is the image of more than one \\(\\mathbf x\\). That is, \\(\\mathrm L\\) is not one-to-one. So the answer is E.

\n\n
\n

Problem 3 (10 points)

\n

Which of the following statements is/are always TRUE?

\n
    \n
  1. If \\(A\\) is a singular \\(8\\times 8\\) matrix, then its last column must be a linear combination of the first seven columns.

  2. \n
  3. Let \\(A\\) be a \\(5\\times 7\\) matrix such that \\(A\\cdot\\pmb x=\\pmb b\\) is consistent for any \\(\\pmb{b}∈\\mathbb{R}^5\\), and let \\(B\\) be a \\(7\\times 11\\) matrix such that \\(B\\cdot\\pmb x=\\pmb c\\) is consistent for any \\(\\pmb{c}∈\\mathbb{R}^7\\). Then, the matrix equation \\(AB\\cdot \\pmb x=\\pmb b\\) is consistent for any \\(\\pmb{b}∈\\mathbb{R}^5\\).

  4. \n
  5. For any \\(m\\times n\\) matrix \\(A\\), the dimension of the null space of \\(A\\) equals the dimension of the null space of its transpose \\(A^T\\).

  6. \n
  7. If \\(A\\) is an \\(m\\times n\\) matrix, then the set \\({A\\cdot\\pmb x|\\pmb x∈\\mathbb{R}^n}\\) is a subspace of \\(\\mathbb{R}^m\\).

  8. \n
\n
    \n
  • A. (i) only
  • \n
  • B. (i) and (ii) only
  • \n
  • C. (iv) only
  • \n
  • D. (ii) and (iv) only
  • \n
  • E. (iii) and (iv) only
  • \n
\n

Problem 3 Solution

\n
\n

For (i), a singular matrix \\(A\\) is noninvertible and has \\(det(A)=0\\). By Theorem 8 of Section 2.3, the columns of \\(A\\) form a linearly dependent set. Denote \\(A=[\\pmb{v}_1\\cdots\\pmb{v}_8]\\), then there exist weights \\(c_1, c_2,\\cdots,c_8\\), not all zero, such that \\[c_1\\pmb{v}_1+c_2\\pmb{v}_2+\\cdots+c_8\\pmb{v}_8=\\pmb{0}\\] Does this imply that statement (i) is true? No! If \\(c_8\\) is 0, \\(\\pmb{v}_8\\) is NOT a linear combination of the columns \\(\\pmb{v}_1\\) to \\(\\pmb{v}_7\\).

\n

For (ii), since \\(AB\\cdot\\pmb x=A(B\\pmb{x})=A\\pmb c=\\pmb b\\). the consistency holds for the new \\(5\\times 11\\) matrix \\(AB\\) as well. It is true.

\n

For (iii), since \\(A\\) is a \\(m\\times n\\) matrix, \\(A^T\\) is a \\(n\\times m\\) matrix. From Section 2.9 Dimension and Rank, we know that \"If a matrix \\(A\\) has \\(n\\) columns, then \\(\\mathrm rank\\,A+\\mathrm{dim\\,Nul}\\,A= n\\).\" From this, we can list \\[\\begin{align}\n\\mathrm{dim\\,Nul}\\,A&=n-rank\\,A\\\\\n\\mathrm{dim\\,Nul}\\,A^T&=m-rank\\,A^T\n\\end{align}\\] As these two dimension numbers are not necessarily the same, (iii) is not true.

\n

For (iv), we can first review the definition of subspace. From Section 2.8 Subspaces of \\(\\mathbb R^n\\),

\n
\n

A subspace of \\(\\mathbb R^n\\) is any set \\(H\\) in \\(\\mathbb R^n\\) that has three properties:
\na. The zero vector is in \\(H\\).
\nb. For each \\(\\pmb u\\) and \\(\\pmb v\\) in \\(H\\), the sum \\(\\pmb u+\\pmb v\\) is in \\(H\\).
\nc. For each \\(\\pmb u\\) in \\(H\\) and each scalar \\(c\\), the vector \\(c\\pmb u\\) is in H.

\n
\n

Denote \\(\\pmb u=A\\pmb x\\), \\(\\pmb v=A\\pmb y\\), we have \\[\\begin{align}\nA\\cdot\\pmb{0}&=\\pmb{0}\\\\\n\\pmb u+\\pmb v&=A\\pmb{x}+A\\pmb{y}=A(\\pmb{x}+\\pmb{y})\\\\\nc\\pmb u&=cA\\pmb{x}=A(c\\pmb x)\n\\end{align}\\] All the results on the right side are in the set as well. This proves that (iv) is true.

\n

As both (ii) and (iv) are true, the answer is D.

\n\n
\n

Problem 4 (10 points)

\n

Compute the determinant of the given matrix \\(\\begin{bmatrix}5 &7 &2 &2\\\\0 &3 &0 &-4\\\\-5 &-8 &0 &3\\\\0 &5 &0 &-6\\\\\\end{bmatrix}\\)

\n
    \n
  • A. \\(-20\\)
  • \n
  • B. \\(20\\)
  • \n
  • C. \\(18\\)
  • \n
  • D. \\(2\\)
  • \n
  • E. \\(0\\)
  • \n
\n

Problem 4 Solution

\n
\n

Notice that the third column of the given matrix has all entries equal to zero except \\(a_{13}\\). Taking advantage of this, we can do a cofactor expansion down the third column, then continue to do cofactor expansion with the \\(3\\times3\\) submatrix \\[\\begin{align}\n\\begin{vmatrix}5 &7 &\\color{fuchsia}2 &2\\\\0 &3 &0 &-4\\\\-5 &-8 &0 &3\\\\0 &5 &0 &-6\\\\\\end{vmatrix}&=(-1)^{1+3}\\cdot{\\color{fuchsia}2}\\cdot\\begin{vmatrix}0 &3 &-4\\\\\\color{blue}{-5} &-8 &3\\\\0 &5 &-6\\\\\\end{vmatrix}\\\\\n&=2\\cdot(-1)^{2+1}\\cdot({\\color{blue}{-5}})\\begin{vmatrix}3 &-4\\\\5 &-6\\\\\\end{vmatrix}=20\n\\end{align}\\] So the answer is B.

\n

📝Notes:This problem is directly taken from the textbook. It is the Practice Problem of Section 3.1 Introduction to Determinants.

\n\n
\n

Problem 5 (10 points)

\n

Which of the following statements is always TRUE

\n

A. If \\(A\\) is an \\(n\\times n\\) matrix with all entries being positive, then \\(\\det(A)>0\\).

\n

B. If \\(A\\) and \\(B\\) are two \\(n\\times n\\) matrices with \\(\\det(A)>0\\) and \\(\\det(B)>0\\), then also \\(\\det(A+B)>0\\).

\n

C. If \\(A\\) and \\(B\\) are two \\(n\\times n\\) matrices such that \\(AB=0\\), then both \\(A\\) and \\(B\\) are singular.

\n

D. If rows of an \\(n\\times n\\) matrix \\(A\\) are linearly independent, then \\(\\det(A^{T}A)>0\\).

\n

E. If \\(A\\) is an \\(n\\times n\\) matrix with \\(A^2=I_n\\), then \\(\\det(A)=1\\).

\n

Problem 5 Solution

\n
\n

Let's analyze the statements one by one.

\n
    \n
  • A is false. It is trivial to find a \\(2\\times 2\\) example to disprove it, such as \\[\\begin{vmatrix}1 &2\\\\3 &4\\\\\\end{vmatrix}=1\\times 4-2\\times 3=-2\\]

  • \n
  • For B, as stated in Section 3 Properties of Determinants \"\\(\\det(A+B)\\) is not equal to \\(\\det(A)+\\det(B)\\), in general\", this statement is not necessarily true. On the contrary, we can have a simple case like \\(A=\\begin{bmatrix}1 &0\\\\0 &1\\\\\\end{bmatrix}\\) and \\(B=\\begin{bmatrix}-1 &0\\\\0 &-1\\\\\\end{bmatrix}\\), then \\(\\det(A+B)=0\\).

  • \n
  • C is also false since B could be a zero matrix. If that is the case, A is not necessarily singular.

  • \n
  • For D, first with the linearly independent property, we can see \\(\\det(A)\\neq 0\\). Secondary, the multiplicative property gives \\(\\det(A^{T}A)=\\det(A^{T})\\det(A)=(\\det(A))^2\\). So it is true that \\(\\det(A^{T}A) > 0\\).

  • \n
  • For E, from \\(A^2=I_n\\), we can deduce \\(\\det(A^{2})=(\\det(A))^2=1\\), so \\(\\det(A)=\\pm 1\\). For example, if \\(A=\\begin{bmatrix}1 &0\\\\0 &-1\\\\\\end{bmatrix}\\), then \\(\\det(A)=-1\\). This statement is false.

  • \n
\n

So we conclude that the answer is D.

\n\n
\n

Problem 6 (10 points)

\n

Let \\(A=\\begin{bmatrix}1 &2 &6\\\\2 &6 &3\\\\3 &8 &10\\\\\\end{bmatrix}\\) and let its inverse \\(A^{-1}=[b_{ij}]\\). Find \\(b_{12}\\)

\n
    \n
  • A. \\(14\\)
  • \n
  • B. \\(-14\\)
  • \n
  • C. \\(1\\)
  • \n
  • D. \\(-1\\)
  • \n
  • E. \\(6\\)
  • \n
\n

Problem 6 Solution

\n
\n

According to Theorem 8 of Section 3.3, \\(A^{-1}=\\frac{\\large{1}}{\\large{\\mathrm{det}\\,A}}\\mathrm{adj}\\,A\\). Here the adjugate matrix \\(\\mathrm{adj}\\, A\\) is the transpose of the matrix of cofactors. Hence \\[b_{12}=\\frac{C_{21}}{\\mathrm{det}\\,A}\\]

\n

First computer the cofactor \\[C_{21}=(-1)^{2+1}\\begin{vmatrix}2 &6\\\\8 &10\\end{vmatrix}=(-1)\\cdot(20-48)=28\\] Now computer the determinant efficiently with row operations (Theorem 3 of Section 3.2) for \\(A\\) \\[\n{\\mathrm{det}\\,A}=\n\\begin{vmatrix}1 &2 &6\\\\2 &6 &3\\\\3 &8 &10\\\\\\end{vmatrix}=\n\\begin{vmatrix}1 &2 &6\\\\0 &2 &-9\\\\0 &2 &-8\\\\\\end{vmatrix}=\n\\begin{vmatrix}\\color{blue}1 &2 &6\\\\0 &\\color{blue}2 &-9\\\\0 &0 &\\color{blue}1\\\\\\end{vmatrix}=\\color{blue}1\\cdot\\color{blue}2\\cdot\\color{blue}1=2\n\\] So \\(C_{21}=28/2=14\\), the answer is A.

\n\n
\n

Problem 7 (10 points)

\n

Let \\(\\pmb{v_1}=\\begin{bmatrix}1\\\\2\\\\5\\\\\\end{bmatrix}\\), \\(\\pmb{v_2}=\\begin{bmatrix}-2\\\\-3\\\\1\\\\\\end{bmatrix}\\) and \\(\\pmb{x}=\\begin{bmatrix}-4\\\\-5\\\\13\\\\\\end{bmatrix}\\), and \\(\\pmb{B}=\\{\\pmb{v_1},\\pmb{v_2}\\}\\). Then \\(\\pmb B\\) is a basis for \\(H=\\mathrm{span}\\{\\mathbf{v_1,v_2}\\}\\). Determine if \\(\\pmb x\\) is in \\(H\\), and if it is, find the coordinate vector of \\(\\pmb x\\) relative to B.

\n
    \n
  • A. \\([\\pmb x]_B=\\begin{bmatrix}1\\\\2\\\\\\end{bmatrix}\\)
  • \n
  • B. \\([\\pmb x]_B=\\begin{bmatrix}2\\\\1\\\\\\end{bmatrix}\\)
  • \n
  • C. \\([\\pmb x]_B=\\begin{bmatrix}2\\\\2\\\\\\end{bmatrix}\\)
  • \n
  • D. \\([\\pmb x]_B=\\begin{bmatrix}3\\\\2\\\\\\end{bmatrix}\\)
  • \n
  • E. \\([\\pmb x]_B=\\begin{bmatrix}2\\\\3\\\\\\end{bmatrix}\\)
  • \n
\n

Problem 7 Solution

\n
\n

By definition in Section 1.3, \\(\\mathrm{Span}\\{\\pmb{v_1,v_2}\\}\\) is the collection of all vectors that can be written in the form \\(c_1\\mathbf{v_1}+c_2\\mathbf{v_2}\\) with \\(c_1,c_2\\) scalars. So asking whether a vector \\(\\pmb x\\) is in \\(\\mathrm{Span}\\{\\pmb{v_1,v_2}\\}\\) amounts to asking whether the vector equation \\[c_1\\pmb{v_1}+c_2\\pmb{v_2}=\\pmb{x}\\] has a solution. To answer this, row reduce the augmented matrix \\([\\pmb{v_1}\\,\\pmb{v_2}\\,\\pmb{x}]\\): \\[\n\\begin{bmatrix}1 &-2 &-4\\\\2 &-3 &-5\\\\5 &1 &13\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-2 &-4\\\\0 &1 &3\\\\0 &11 &33\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-2 &-4\\\\0 &1 &3\\\\0 &0 &0\\\\\\end{bmatrix}\\sim\n\\] We have a unique solution \\(c_1=2\\), \\(c_2=3\\). So the answer is E.

\n

📝Notes:This problem is similar to Example 6 of Section 1.3 Vector Equations.

\n\n
\n

Problem 8 (10 points)

\n

Let \\(T: \\mathbb R^2\\to\\mathbb R^3\\) be the linear tranformation for which \\[\nT\\left(\\begin{bmatrix}1\\\\1\\\\\\end{bmatrix}\\right)=\n\\begin{bmatrix}3\\\\2\\\\1\\\\\\end{bmatrix}\\quad \\mathrm{and}\\quad\nT\\left(\\begin{bmatrix}1\\\\2\\\\\\end{bmatrix}\\right)=\n\\begin{bmatrix}1\\\\0\\\\2\\\\\\end{bmatrix}.\n\\] (4 points)(1) Let \\(A\\) be the standard matrix of \\(T\\), find \\(A\\).

\n

(2 points)(2) Find the image of the vector \\(\\pmb u=\\begin{bmatrix}1\\\\3\\\\\\end{bmatrix}\\).

\n

(4 points)(3) Is the vector \\(\\pmb b=\\begin{bmatrix}0\\\\-2\\\\5\\\\\\end{bmatrix}\\) in the range of \\(T\\)? If so, find all the vectors \\(\\pmb x\\) in \\(\\mathbb R^2\\) such that \\(T(\\pmb x)=\\pmb b\\)

\n

Problem 8 Solution

\n
\n
    \n
  1. Referring to Theorem 10 of Section 1.9 The Matrix of a Linear Transformation, we know that \\[A=[T(\\pmb{e}_1)\\quad\\dots\\quad T(\\pmb{e}_n)]\\] So if we can find \\(T(\\pmb{e}_1)\\) and \\(T(\\pmb{e}_2)\\), we obtain \\(A\\). Remember the property \\[T(c\\pmb u+d\\pmb v)=cT(\\pmb u)+dT(\\pmb v)\\]

    \n

    We can use this property to find \\(A\\). First, it is trivial to see that \\[\\begin{align}\n \\pmb{e}_1&=\\begin{bmatrix}1\\\\0\\end{bmatrix}\n =2\\begin{bmatrix}1\\\\1\\end{bmatrix}-\\begin{bmatrix}1\\\\2\\end{bmatrix}\\\\\n \\pmb{e}_2&=\\begin{bmatrix}0\\\\1\\end{bmatrix}\n =-\\begin{bmatrix}1\\\\1\\end{bmatrix}+\\begin{bmatrix}1\\\\2\\end{bmatrix}\n \\end{align}\\] Then apply the property and compute \\[\\begin{align}\n T(\\pmb{e}_1)&=2T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)-T\\left(\\begin{bmatrix}1\\\\2\\end{bmatrix}\\right)=\\begin{bmatrix}5\\\\4\\\\0\\end{bmatrix}\\\\\n T(\\pmb{e}_2)&=-T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)+T\\left(\\begin{bmatrix}1\\\\2\\end{bmatrix}\\right)=\\begin{bmatrix}-2\\\\-2\\\\1\\end{bmatrix}\n \\end{align}\\] So \\(A\\) is \\(\\begin{bmatrix}5 &-2\\\\4 &-2\\\\0 &1\\end{bmatrix}\\).

  2. \n
  3. The image of the vector \\(\\pmb u\\) can be obtained by \\(A\\pmb u\\), the result is \\[A\\pmb u=\\begin{bmatrix}5 &-2\\\\4 &-2\\\\0 &1\\end{bmatrix}\\begin{bmatrix}1\\\\3\\\\\\end{bmatrix}=\\begin{bmatrix}-1\\\\-2\\\\3\\\\\\end{bmatrix}\\]

  4. \n
  5. This is the case of \\(A\\pmb x=\\pmb b\\) and we need to solve it. The augmented matrix here is \\[\\begin{bmatrix}5 &-2 &0\\\\4 &-2 &-2\\\\0 &1 &5\\end{bmatrix}\\] This has unique solution \\(\\begin{bmatrix}2\\\\5\\\\\\end{bmatrix}\\). So the vector \\(\\pmb b\\) is in the span of \\(T\\).

  6. \n
\n\n
\n

Problem 9 (10 points)

\n

Consider the linear system \\[\n\\begin{align}\nx + 2y +3z &= 2\\\\\ny+az &= -4\\\\\n2x+5y+a^{2}z &= a-3\n\\end{align}\n\\] (4 points)(1) Find a row echelon form for the augmented matrix of the system.

\n

(2 points)(2) For which value(s) of \\(a\\) does this system have a infinite number of solutions?

\n

(2 points)(3) For which value(s) of \\(a\\) does this system have no solution?

\n

(2 points)(4) For which value(s) of \\(a\\) does this system have a unique solution?

\n

Problem 9 Solution

\n
\n
    \n
  1. The augmented matrix and the row reduction results can be seen below \\[\n\\begin{bmatrix}1 &2 &3 &2\\\\0 &1 &a &-4\\\\2 &5 &a^2 &a-3\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &3 &2\\\\0 &1 &a &-4\\\\0 &1 &a^2-6 &a-7\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &3 &2\\\\0 &1 &a &-4\\\\0 &0 &a^2-a-6 &a-3\\\\\\end{bmatrix}\n\\] The pivots are \\(1\\), \\(1\\), and \\(a2-a-6\\).

  2. \n
  3. Look at the last row of the row echelon form, we can write it as \\((a-3)(a+2)z=(a-3)\\). Obviously if \\(a=3\\), \\(z\\) can be any number. So this system has an infinite number of solutions when \\(a=3\\).

  4. \n
  5. If \\(a=-2\\), the equation becomes \\(0\\cdot z=-5\\). This is impossible. So the system is inconsistent and has no solution when \\(a=-2\\).

  6. \n
  7. If \\(a\\neq -2\\) and \\(a\\neq 3\\)\\(z=\\frac 1 {a+2}\\), we can deduce unique solution for this system

  8. \n
\n\n
\n

Problem 10 (10 points)

\n

Let \\[\nA=\\begin{bmatrix}1 &2 &0 &-1 &2\\\\2 &3 &1 &-3 &7\\\\3 &4 &1 &-3 &9\\\\\\end{bmatrix}\n\\]

\n

(5 points)(1) Find the REDUCED row echelon form for the matrix \\(A\\).

\n

(5 points)(2) Find a basis for the null space of \\(A\\)

\n

Problem 10 Solution

\n
\n
    \n
  1. The row reduction is completed next. The symbol ~ before a matrix indicates that the matrix is row equivalent to the preceding matrix. \\[\n\\begin{bmatrix}1 &2 &0 &-1 &2\\\\2 &3 &1 &-3 &7\\\\3 &4 &1 &-3 &9\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &0 &-1 &2\\\\0 &-1 &1 &-1 &3\\\\0 &-2 &1 &0 &3\\\\\\end{bmatrix}\\sim\\begin{bmatrix}1 &2 &0 &-1 &2\\\\0 &1 &-1 &1 &-3\\\\0 &0 &1 &-2 &3\\\\\\end{bmatrix}\n\\] \\[\\sim\n\\begin{bmatrix}1 &2 &0 &-1 &2\\\\0 &1 &0 &-1 &0\\\\0 &0 &1 &-2 &3\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0 &1 &2\\\\0 &1 &0 &-1 &0\\\\0 &0 &1 &-2 &3\\\\\\end{bmatrix}\n\\]

  2. \n
  3. Referring to Section 2.8 Subspaces of \\(\\mathbb R^n\\), by definition the null space of a matrix \\(A\\) is the set Nul \\(A\\) of all solutions of the homogeneous equation \\(A\\pmb{x}=\\pmb{0}\\). Also \"A basis for a subspace \\(H\\) of \\(\\mathbb R^n\\) is a linearly independent set in \\(H\\) that spans \\(H\\)\".

    \n

    Now write the solution of \\(A\\mathrm x=\\pmb 0\\) in parametric vector form \\[[A\\;\\pmb 0]\\sim\\begin{bmatrix}1 &0 &0 &1 &2 &0\\\\0 &1 &0 &-1 &0 &0\\\\0 &0 &1 &-2 &3 &0\\\\\\end{bmatrix}\\]

    \n

    The general solution is \\(x_1=-x_4-2x_5\\), \\(x_2=x_4\\), \\(x_3=2x_4-3x_5\\), with \\(x_4\\) and \\(x_5\\) free. This can be written as \\[\n \\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n \\begin{bmatrix}-x_4-2x_5\\\\x_4\\\\2x_4-3x_5\\\\x_4\\\\x_5\\end{bmatrix}=\n x_4\\begin{bmatrix}-1\\\\1\\\\2\\\\1\\\\0\\end{bmatrix}+\n x_5\\begin{bmatrix}-2\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\n \\] So the basis for Nul \\(A\\) is \\[\n \\begin{Bmatrix}\\begin{bmatrix}-1\\\\1\\\\2\\\\1\\\\0\\end{bmatrix},\n \\begin{bmatrix}-2\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\n \\]

  4. \n
\n

📝Notes:This problem is similar to Example 6 of Section 2.8 Subspaces of \\(\\mathbb R^n\\). Read the solution for that example to get a deep understanding of this problem. Also pay attention to Example 7, Example 8, Theorem 13, and the Warning below this theorem in the same section.

\n
\n

Warning: Be careful to use pivot columns of \\(A\\) itself for the basis of Col \\(A\\). The columns of an echelon form \\(B\\) are often not in the column space of \\(A\\).

\n
\n\n
\n

Summary

\n

This test set focuses on the following points of linear algebra:

\n
    \n
  • Systems of linear equations\n
      \n
    • Elementary row operations, system consistency
    • \n
    • Row echelon form, and reduced row echelon form
    • \n
  • \n
  • Column vector, linear combinations of vectors, and span
  • \n
  • Matrix equation, solution existence, linear independence
  • \n
  • Linear transformation\n
      \n
    • Image, range, identity matrix, standard matrix
    • \n
    • Onto and one-to-one mappings
    • \n
  • \n
  • Matrix operations, the inverse of a matrix
  • \n
  • Subspace and basis, null space, dimension, and rank
  • \n
  • Determinant, Cramer's rule, adjugate matrix, and inverse formula
  • \n
\n

As can be seen, it has a very decent coverage of the basic ideas of linear algebra. So this set of exam problems provides a good test of students' knowledge of linear algebra.

\n

One thing I would like to highlight for preparing for the first exam of linear algebra is to have a complete understanding of two aspects of matrix equations. It is like two profiles of one object. As can be seen in the following snapshot taken from the textbook, a matrix equation can represent a linear combination of its column vectors. From a different viewpoint, it is used to describe the transformation that maps a vector in one space to a new vector in the other space.

\n

\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Fall 2022 Midterm II Solutions","url":"/en/2024/02/10/Purdue-MA265-2022-Fall-Midterm2/","content":"

Here comes the solution and analysis for Purdue MA 26500 Fall 2022 Midterm II. This second midterm covers topics in Chapter 4 (Vector Spaces) and Chapter 5 (Eigenvalues and Eigenvectors) of the textbook.

\n

Introduction

\n

Purdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.

\n

Textbook and Study Guide

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.

\n
\n

Exam Information

\n

MA 26500 midterm II covers the topics of Sections 4.1 – 5.7 in the textbook. It is usually scheduled at the beginning of the thirteenth week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.

\n

Based on the knowledge of linear equations and matrix algebra learned in the book chapters 1 and 2, Chapter 4 leads the student to a deep dive into the vector space framework. Chapter 5 introduces the important concepts of eigenvectors and eigenvalues. They are useful throughout pure and applied mathematics. Eigenvalues are also used to study differential equations and continuous dynamical systems, they provide critical information in engineering design,

\n

Reference Links

\n\n

Fall 2022 Midterm II Solutions

\n

Problem 1 (10 points)

\n

Let \\[A=\\begin{bmatrix}1 &0 &2 &0 &-1\\\\1 &2 &4 &-2 &-1\\\\2 &3 &7 &-3 &-2\\end{bmatrix}\\] Let \\(a\\) be the rank of \\(A\\) and \\(b\\) be the nullity of \\(A\\), find \\(5b-3a\\)

\n
    \n
  • A. 25
  • \n
  • B. 17
  • \n
  • C. 9
  • \n
  • D. 1
  • \n
  • E. 0
  • \n
\n

Problem 1 Solution

\n
\n

Do row reduction as follows:

\n
    \n
  1. Add \\(-1\\) times row 1 to row 2
  2. \n
  3. Add \\(-2\\) times row 1 to row 2
  4. \n
  5. Scale row 2 by \\(\\frac{1}{2}\\)
  6. \n
  7. Add \\(-3\\) times row 2 to row 3
  8. \n
\n

\\[\n\\begin{bmatrix}1 &0 &2 &0 &-1\\\\1 &2 &4 &-2 &-1\\\\2 &3 &7 &-3 &-2\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &0 &-1\\\\0 &2 &2 &-2 &0\\\\0 &3 &3 &-3 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}1 &0 &2 &0 &-1\\\\0 &\\color{fuchsia}1 &1 &-1 &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\n\\]

\n

So we have 2 pivots, the rank is 2 and the nullity is 3. This results in \\(5b-3a=5\\cdot 3-3\\cdot 2=9\\).

\n

The answer is C.

\n\n
\n

Problem 2 (10 points)

\n

Let \\(\\pmb u=\\begin{bmatrix}2\\\\0\\\\1\\end{bmatrix}\\), \\(\\pmb v=\\begin{bmatrix}3\\\\1\\\\0\\end{bmatrix}\\), and \\(\\pmb w=\\begin{bmatrix}1\\\\-1\\\\c\\end{bmatrix}\\) where \\(c\\) is a real number. The set \\(\\{\\pmb u, \\pmb v, \\pmb w\\}\\) is a basis for \\(\\mathbb R^3\\) provided that \\(c\\) is not equal

\n
    \n
  • A. \\(-2\\)
  • \n
  • B. \\(2\\)
  • \n
  • C. \\(-3\\)
  • \n
  • D. \\(3\\)
  • \n
  • E. \\(-1\\)
  • \n
\n

Problem 2 Solution

\n
\n

For set \\(\\{\\pmb u, \\pmb v, \\pmb w\\}\\) to be a basis for \\(\\mathbb R^3\\), the three vectors should be linearly independent. Let's create a matrix with these vectors as columns, then do row reduction like below \\[\n\\begin{bmatrix}2 &3 &1\\\\0 &1 &-1\\\\1 &0 &c\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &c\\\\0 &1 &-1\\\\2 &3 &1\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &c\\\\0 &1 &-1\\\\0 &3 &1-2c\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &c\\\\0 &1 &-1\\\\0 &0 &4-2c\\end{bmatrix}\n\\]

\n

As can be seen, we need 3 pivots to make these column vectors linearly independent. If \\(c\\) is 2, the last row above has all-zero entries, there would be only 2 pivots. So C cannot be 2 for these three vectors to be linearly independent.

\n

The answer is B.

\n\n
\n

Problem 3 (10 points)

\n

Which of the following statements is always TRUE?

\n
    \n
  • A. If \\(A\\pmb x=\\lambda\\pmb x\\) for some vector \\(\\pmb x\\), then \\(\\lambda\\) is an eigenvalue of \\(A\\).
  • \n
  • B. If \\(\\pmb v\\) is an eigenvector corresponding to eigenvalue 2, then \\(-\\pmb v\\) is an eigenvector corresonding to eigenvalue \\(-2\\).
  • \n
  • C. If \\(B\\) is invertible, then matrix \\(A\\) and \\(B^{-1}AB\\) could have different sets of eigenvalues.
  • \n
  • D. If \\(\\lambda\\) is an eigenvalue of matrix \\(A\\), then \\(\\lambda^2\\) is an eigenvalue of matrix \\(A^2\\).
  • \n
  • E. If \\(-5\\) is an eigenvalue of matrix \\(B\\), then matrix \\(B-5I\\) is not invertible.
  • \n
\n

Problem 3 Solution

\n
\n

Per definitions in 5.1 \"Eigenvectors and Eigenvalues\":

\n
\n

An eigenvector of an \\(n\\times n\\) matrix \\(A\\) is a nonzero vector \\(\\pmb x\\) such that \\(A\\pmb x=\\lambda\\pmb x\\) for some scalar \\(\\lambda\\). A scalar \\(\\lambda\\) is called an eigenvalue of \\(A\\) if there is a nontrivial solution \\(\\pmb x\\) of \\(A\\pmb x=\\lambda\\pmb x\\); such an \\(\\pmb x\\) is called an eigenvector corresponding to \\(\\lambda\\).

\n
\n

Statement A is missing the \"nonzero\" keyword, so it is NOT always TRUE.

\n

For Statement B, given \\(A\\pmb v=2\\pmb v\\), we can obtain \\(A(\\pmb{-v})=2(\\pmb{-v})\\). The eigenvalue is still 2, not \\(-2\\). This statement is FALSE.

\n

Statement C involves the definition of Similarity. Denote \\(P=B^{-1}AB\\), we have \\[BPB^{-1}=BB^{-1}ABB^{-1}=A\\] So \\(A\\) and \\(P\\) are similar. Similar matrices have the same eigenvalues (Theorem 4 in Section 5.2 \"The Characteristic Equation\"). Statement C is FALSE

\n
\n

This can be proved easily, as seen below \\[\\begin{align}\n\\det (A-\\lambda I)&=\\det (BPB^{-1}-\\lambda I)=\\det (BPB^{-1}-\\lambda BB^{-1})\\\\\n &=\\det(B)\\det(P-\\lambda I)\\det(B^{-1})\\\\\n &=\\det(B)\\det(B^{-1})\\det(P-\\lambda I)\n\\end{align}\\] Since \\(\\det(B)\\det(B^{-1})=\\det(BB^{-1})=\\det I=1\\), we see that \\(\\det (A-\\lambda I)=\\det(P-\\lambda I)\\). ■

\n
\n

For Statement D, given \\(A\\pmb x=\\lambda\\pmb x\\), we can do the following deduction \\[A^2\\pmb x=AA\\pmb x=A\\lambda\\pmb x=\\lambda A\\pmb x=\\lambda^2\\pmb x\\] So it is always TRUE that \\(\\lambda^2\\) is an eigenvalue of matrix \\(A^2\\).

\n

Statement E is FALSE. An eigenvalue \\(-5\\) means matrix \\(B-(-5)I\\) is not invertible since \\(\\det(B-(-5)I)=\\det(B+5I)=0\\). But the statement refers to a different matrix \\(B-5I\\).

\n

The answer is D.

\n\n
\n

Problem 4 (10 points)

\n

Let \\(\\mathbb P_3\\) be the vector space of all polynomials of degree at most 3. Which of the following subsets are subspaces of \\(\\mathbb P_3\\)?

\n
    \n
  1. A set of polynomials in \\(\\mathbb P_3\\) satisfying \\(p(0)=p(1)\\).
    \n
  2. \n
  3. A set of polynomials in \\(\\mathbb P_3\\) satisfying \\(p(0)p(1)=0\\).
    \n
  4. \n
  5. A set of polynomials in \\(\\mathbb P_3\\) with integer coefficients.
  6. \n
\n
    \n
  • A. (i) only
  • \n
  • B. (i) and (ii) only
  • \n
  • C. (i) and (iii) only
  • \n
  • D. (ii) only
  • \n
  • E. (ii) and (iii) only
  • \n
\n

Problem 4 Solution

\n
\n

Per the definition of Subspace in Section 4.1 \"Vector Spaces and Subspaces\"

\n
\n

A subspace of a vector space \\(V\\) is a subset \\(H\\) of \\(V\\) that has three properties:
\na. The zero vector of \\(V\\) is in \\(H\\).
\nb. \\(H\\) is closed under vector addition. That is, for each \\(\\pmb u\\) and \\(\\pmb v\\) in \\(H\\), the sum \\(\\pmb u + \\pmb v\\) is in \\(H\\).
\nc. \\(H\\) is closed under multiplication by scalars. That is, for each \\(\\pmb u\\) in \\(H\\) and each scalar \\(c\\), the vector \\(c\\pmb u\\) is in \\(H\\).

\n
\n

So to be qualified as the subspace, the subset should have all the above three properties. Denote the polynomials as \\(p(x)=a_0+a_1x+a_2x^2+a_3x^3\\).

\n
    \n
  • (i) Since \\(p(0)=p(1)\\), we have \\(a_0=a_0+a_1+a_2+a_3\\), so \\(a_1+a_2+a_3=0\\).

    \n
      \n
    • Obviously, it satisfies the first property as if \\(a_i=0\\) for all \\(i\\), \\(a_1+a_2+a_3=0\\) is true as well.
    • \n
    • Now assume \\(p_1(x)\\) and \\(p_2(x)\\) are two polynomials in this set and \\[\np_1(x)=a_0+a_1x+a_2x^2+a_3x^3\\\\\np_2(x)=b_0+b_1x+b_2x^2+b_3x^3\n\\] So we have \\(a_1+a_2+a_3=0\\) and \\(b_1+b_2+b_3=0\\). Then define a third polynomial \\[\\begin{align}\np_3(x)&=p_1(x)+p_2(x)\\\\\n&=(a_0+b_0)+(a_1+b_1)x+(a_2+b_2)x^2+(a_3+b_3)x^3\\\\\n&=c_0+c_1x+c_2x^2+c_3x^3\n\\end{align}\\] It is true that \\(c_1+c_2+c_3=0\\) as well. So the set has the second property.
    • \n
    • This set does have the third property since \\(cp(x)\\) has \\(ca_1+ca_2+ca_3=0\\) and it is also in the same set.
    • \n
    \n

    This proves that set (i) is a subspace of \\(\\mathbb P_3\\).

  • \n
  • (ii) From \\(p(0)p(1)=0\\), we can deduce that \\(a_0(a_0+a_1+a_2+a_3)=0\\). So any polynomial in this set should satisfy this condition.

    \n
      \n
    • Obviously, it satisfies the first property as if \\(a_i=0\\) for all \\(i\\), \\(a_0(a_0+a_1+a_2+a_3)=0\\) is true as well.
    • \n
    • With the same notation of \\(p_1(x)\\), \\(p_2(x)\\) and \\(p_3(x)\\). We have \\[\\begin{align}\nc_0(c_0+c_1+c_2+c_3)&=(a_0+b_0)(a_0+b_0+a_1+b_1+a_2+b_2+a_3+b_3)\\\\\n&=(a_0+b_0)((a_0+a_1+a_2+a_3)+(b_0+b_1++b_2+b_3))\\\\\n&=a_0(a_0+a_1+a_2+a_3)+a_0(b_0+b_1++b_2+b_3)+b_0(a_0+a_1+a_2+a_3)+b_0(b_0+b_1++b_2+b_3)\\\\\n&=a_0(b_0+b_1+b_2+b_3)+b_0(a_0+a_1+a_2+a_3)\n\\end{align}\\] If \\(a_0=0\\) and \\(b_0\\ne 0\\), the above ends up with \\(b_0(a_1+a_2+a_3)\\), which is not necessary equal 0. So this polynomial in this set does NOT have the second property.
    • \n
    \n

    This proves that set (ii) is NOT a subspace of \\(\\mathbb P_3\\).

  • \n
  • (iii) It is easy to tell that this set is NOT a subspace of \\(\\mathbb P_3\\). If we do multiplication by floating-point scalars, the new polynomial does not necessarily have an integer coefficient for each term and might not be in the same set.

  • \n
\n

So the answer is A.

\n\n
\n

Problem 5 (10 points)

\n

Consider the differential equation \\[\n\\begin{bmatrix}x'(t)\\\\y'(t)\\end{bmatrix}=\n\\begin{bmatrix}1 &3\\\\-2 &2\\end{bmatrix}\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}\n\\].

\n

Then the origin is

\n
    \n
  • A. an attractor
  • \n
  • B. a repeller
  • \n
  • C. a saddle point
  • \n
  • D. a spiral point
  • \n
  • E. none of the above
  • \n
\n

Problem 5 Solution

\n
\n

First, write the system as a matrix differential equation \\(\\pmb x'(t)=A\\pmb x(t)\\). We learn from Section 5.7 \"Applications to Differential Equations\" that each eigenvalue–eigenvector pair provides a solution.

\n

Now let's find out the eigenvalues of \\(A\\). From \\(\\det (A-\\lambda I)=0\\), we have \\[\\begin{vmatrix}1-\\lambda &3\\\\-2 &2-\\lambda\\end{vmatrix}=\\lambda^2-3\\lambda+8=0\\] This only gives two complex numbers as eigenvalues \\[\\lambda=\\frac{3\\pm\\sqrt{23}i}{2}\\]

\n

Referring to the Complex Eigenvalues discussion at the end of this section, \"the origin is called a spiral point of the dynamical system. The rotation is caused by the sine and cosine functions that arise from a complex eigenvalue\". Because the complex eigenvalues have a positive real part, the trajectories spiral outward.

\n

So the answer is D.

\n
\n

Refer to the following table for the mapping from \\(2\\times 2\\) matrix eigenvalues to trajectories:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
EigenvaluesTrajectories
\\(\\lambda_1>0, \\lambda_2>0\\)Repeller/Source
\\(\\lambda_1<0, \\lambda_2<0\\)Attactor/Sink
\\(\\lambda_1<0, \\lambda_2>0\\)Saddle Point
\\(\\lambda = a\\pm bi, a>0\\)Spiral (outward) Point
\\(\\lambda = a\\pm bi, a<0\\)Spiral (inward) Point
\\(\\lambda = \\pm bi\\)Ellipses (circles if \\(b=1\\))
\n
\n\n
\n

Problem 6 (10 points)

\n

Which of the following matrices are diagonalizable over the real numbers?

\n
    \n
  1. \\(\\begin{bmatrix}2 &-5\\\\3 &-6\\end{bmatrix}\\) (ii) \\(\\begin{bmatrix}4 &1\\\\0 &4\\end{bmatrix}\\) (iii) \\(\\begin{bmatrix}1 &-1 &3\\\\0 &5 &-2\\\\0 &0 &7\\end{bmatrix}\\) (iv) \\(\\begin{bmatrix}7 &1 &1\\\\0 &2 &2\\\\0 &1 &3\\end{bmatrix}\\)
  2. \n
\n
    \n
  • A. (i) and (iii) only
  • \n
  • B. (iii) and (iv) only
  • \n
  • C. (i), (iii) and (iv) only
  • \n
  • D. (i), (ii) and (iii) only
  • \n
  • E. (i), (ii) and (iv) only
  • \n
\n

Problem 6 Solution

\n
\n

This problem tests our knowledge of Theorem 6 of Section 5.3 \"Diagonalization\":

\n
\n

An \\(n\\times n\\) matrix with \\(n\\) distinct eigenvalues is diagonalizable.

\n
\n

So let's find out the eigenvalues for each matrix:

\n
    \n
    1. \n
    2. From the equation \\(\\det A-\\lambda I=0\\), we can obtain \\[\\begin{vmatrix}2-\\lambda &-5\\\\3 &-6-\\lambda\\end{vmatrix}=(\\lambda-2)(\\lambda+6)+15=(\\lambda+1)\\lambda+3)=0\\] This leads to two roots \\(\\lambda_1=-1\\), \\(\\lambda_2=-3\\).
    3. \n
  • \n
    1. \n
    2. Since this is a triangular matrix, the eigenvalue is just 4, with multiplicity 2.
    3. \n
  • \n
    1. \n
    2. For the same reason, this \\(3\\times 3\\) matrix has eigenvalues 1, 5 and 7.
    3. \n
  • \n
    1. \n
    2. Use cofactor expansion with \\(C_{1,1}\\), we have \\[\\begin{align}\n\\begin{vmatrix}7-\\lambda &1 &1\\\\0 &2-\\lambda &2\\\\0 &1 &3-\\lambda\\end{vmatrix}&=\n(7-\\lambda)(-1)^{1+1}\\begin{vmatrix}2-\\lambda &2\\\\1 &3-\\lambda\\end{vmatrix}\\\\\n&=(7-\\lambda)(\\lambda^2-5\\lambda+6-2)\\\\\n&=(7-\\lambda)(\\lambda-4)(\\lambda-1)\n\\end{align}\\] The eigenvalues are 7, 4, and 1.
    3. \n
  • \n
\n

Now we can see that (i), (iii), and (iv) have distinct eigenvalues, they are diagonalizable matrices.

\n

So the answer is C.

\n\n
\n

Problem 7 (10 points)

\n

A real \\(2\\times 2\\) matrix \\(A\\) has an eigenvalue \\(\\lambda_1=2+i\\) with corresponding eigenvector \\(\\pmb v_1=\\begin{bmatrix}3-i\\\\4+i\\end{bmatrix}\\). Which of the following is the general REAL solution to the system of differential equations \\(\\pmb x'(t)=A\\pmb x(t)\\)

\n
    \n
  • A. \\(c_{1}e^{2t}\\begin{bmatrix}3\\cos t-\\sin t\\\\4\\cos t+\\sin t\\end{bmatrix}+c_{2}e^{2t}\\begin{bmatrix}3\\sin t+\\cos t\\\\4\\sin t-\\cos t\\end{bmatrix}\\)
  • \n
  • B. \\(c_{1}e^{2t}\\begin{bmatrix}-3\\cos t+\\sin t\\\\4\\cos t-\\sin t\\end{bmatrix}+c_{2}e^{2t}\\begin{bmatrix}3\\sin t-\\cos t\\\\4\\sin t-\\cos t\\end{bmatrix}\\)
  • \n
  • C. \\(c_{1}e^{2t}\\begin{bmatrix}3\\cos t-\\sin t\\\\4\\cos t+\\sin t\\end{bmatrix}+c_{2}e^{2t}\\begin{bmatrix}3\\sin t-\\cos t\\\\4\\sin t-\\cos t\\end{bmatrix}\\)
  • \n
  • D. \\(c_{1}e^{2t}\\begin{bmatrix}3\\cos t+\\sin t\\\\4\\cos t-\\sin t\\end{bmatrix}+c_{2}e^{2t}\\begin{bmatrix}3\\sin t+\\cos t\\\\4\\sin t-\\cos t\\end{bmatrix}\\)
  • \n
  • E. \\(c_{1}e^{2t}\\begin{bmatrix}3\\cos t+\\sin t\\\\4\\cos t-\\sin t\\end{bmatrix}+c_{2}e^{2t}\\begin{bmatrix}3\\sin t-\\cos t\\\\4\\sin t+\\cos t\\end{bmatrix}\\)
  • \n
\n

Problem 7 Solution

\n
\n

From Section 5.7 \"Applications to Differential Equations\", we learn that the general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] For a real matrix, complex eigenvalues and associated eigenvectors come in conjugate pairs. Hence we know that \\(\\lambda_2=2-i\\) and \\(\\pmb{v}_2=\\begin{bmatrix}3+i\\\\4-i\\end{bmatrix}\\). However, we do not need these two to find our solution here. The real and imaginary parts of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) are (real) solutions of \\(\\pmb x'(t)=A\\pmb x(t)\\), because they are linear combinations of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) and \\(\\pmb{v}_2 e^{\\lambda_2 t}\\). (See the proof in \"Complex Eigenvalues\" of Section 5.7)

\n

Now use Euler's formula (\\(e^{ix}=\\cos x+i\\sin x\\)), we have \\[\\begin{align}\n\\pmb{v}_1 e^{\\lambda_1 t}\n&=e^{(2+i)t}\\begin{bmatrix}3-i\\\\4+i\\end{bmatrix}\\\\\n&=e^{2t}(\\cos t+i\\sin t)\\begin{bmatrix}3-i\\\\4+i\\end{bmatrix}\\\\\n&=e^{2t}\\begin{bmatrix}(3\\cos t+\\sin t)+(3\\sin t-\\cos t)i\\\\(4\\cos t-\\sin t)+(4\\sin t+\\cos t)i\\end{bmatrix}\n\\end{align}\\] The general REAL solution is the linear combination of the REAL and IMAGINARY parts of the result above, it is \\[c_1 e^{2t}\\begin{bmatrix}3\\cos t+\\sin t\\\\4\\cos t-\\sin t\\end{bmatrix}+\nc_2 e^{2t}\\begin{bmatrix}3\\sin t-\\cos t\\\\4\\sin t+\\cos t\\end{bmatrix}\\]

\n

So the answer is E.

\n\n
\n

Problem 8 (10 points)

\n

Let \\(T: M_{2\\times 2}\\to M_{2\\times 2}\\) be a linear map defined as \\(A\\mapsto A+A^T\\).

\n

(2 points) (1) Find \\(T(\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix})\\)

\n

(4 points) (2) Find a basis for the range of \\(T\\).

\n

(4 points) (3) Find a basis for the kernel of \\(T\\).

\n

Problem 8 Solution

\n
\n
    \n
  1. As the mapping rule is \\(A\\mapsto A+A^T\\), we can directly write down the transformation as below \\[T(\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix})=\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix}+\\begin{bmatrix}1 &2\\\\3 &4\\end{bmatrix}^T=\\begin{bmatrix}2 &5\\\\5 &8\\end{bmatrix}\\]

  2. \n
  3. If we denote the 4 entries of a \\(2\\times 2\\) matrix as \\(\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}\\), the transformation can be written as \\[\\begin{align}\nT(\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix})\n&=\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}+\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}^T=\\begin{bmatrix}2a &b+c\\\\b+c &2d\\end{bmatrix}\\\\\n&=2a\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix}+(b+c)\\begin{bmatrix}0 &1\\\\1 &0\\end{bmatrix}+2d\\begin{bmatrix}0 &0\\\\0 &1\\end{bmatrix}\n\\end{align}\\] So the basis can be the set of three \\(3\\times 3\\) matrices like below \\[\n\\begin{Bmatrix}\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix},\\begin{bmatrix}0 &1\\\\1 &0\\end{bmatrix},\\begin{bmatrix}0 &0\\\\0 &1\\end{bmatrix}\\end{Bmatrix}\n\\]

  4. \n
  5. The kernel (or null space) of such a \\(T\\) is the set of all \\(\\pmb u\\) in vector space \\(V\\) such that \\(T(\\pmb u)=\\pmb 0\\). Write this as \\[T(\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix})=\\begin{bmatrix}2a &b+c\\\\b+c &2d\\end{bmatrix}=\\begin{bmatrix}0 &0\\\\0 &0\\end{bmatrix}\\] This leads to \\(a=d=0\\) and \\(c=-b\\). So the original matrix \\(A\\) that satified this conditioncan be represented as \\(c\\begin{bmatrix}0 &1\\\\-1 &0\\end{bmatrix}\\). This shows that \\(\\begin{bmatrix}0 &1\\\\-1 &0\\end{bmatrix}\\) (or \\(\\begin{bmatrix}0 &-1\\\\1 &0\\end{bmatrix}\\)) is the basis for the null space of \\(T\\).

  6. \n
\n\n
\n

Problem 9 (10 points)

\n

(6 points) (1) Find all the eigenvalues of matrix \\(A=\\begin{bmatrix}4 &0 &0\\\\1 &2 &1\\\\-1 &2 &3\\end{bmatrix}\\), and find a basis for the eigenspace corresponding to each of the eigenvalues.

\n

(4 points) (2) Find an invertible matrix \\(P\\) and a diagonal matrix \\(D\\) such that \\[\n\\begin{bmatrix}4 &0 &0\\\\1 &2 &1\\\\-1 &2 &3\\end{bmatrix}=PDP^{-1}\n\\]

\n

Problem 9 Solution

\n
\n
    \n
  1. Apply the equation \\(\\det A-\\lambda I=0\\), we have \\[\\begin{vmatrix}4-\\lambda &0 &0\\\\1 &2-\\lambda &1\\\\-1 &2 &3-\\lambda\\end{vmatrix}=(4-\\lambda)\\begin{vmatrix}2-\\lambda &1\\\\2 &3-\\lambda\\end{vmatrix}=-(\\lambda-4)^2(\\lambda-1)=0\\] So the eigenvalues are 4 an 1. Now to find eigenvector for each eigenvalue, we take the eigenvalue to the system \\((A-\\lambda I)\\pmb x=\\pmb 0\\) and find the basis vector(s) which would be the eigenvector.\n
      \n
    • For \\(\\lambda_1=\\lambda_2=4\\), we have the new matrix as \\[\\begin{bmatrix}0 &0 &0\\\\1 &-2 &1\\\\-1 &2 &-1\\end{bmatrix}\\sim\n \\begin{bmatrix}0 &0 &0\\\\1 &-2 &1\\\\0 &0 &0\\end{bmatrix}\\] This gives \\(x_1-2x_2+x_3=0\\) with two free variables \\(x_2\\) and \\(x_3\\). Now in parametric vector form, we can obtain \\[\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\end{bmatrix}=\\begin{bmatrix}2x_2-x_3\\\\x_2\\\\x_3\\end{bmatrix}=x_2\\begin{bmatrix}2\\\\1\\\\0\\end{bmatrix}+x_3\\begin{bmatrix}-1\\\\0\\\\1\\end{bmatrix}\\] A basis is \\(\\begin{Bmatrix}\\begin{bmatrix}2\\\\1\\\\0\\end{bmatrix},\\begin{bmatrix}-1\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\\).
    • \n
    • For \\(\\lambda_3=1\\), the new matrix is \\[\\begin{bmatrix}3 &0 &0\\\\1 &1 &1\\\\-1 &2 &2\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\0 &1 &1\\\\0 &2 &2\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\0 &1 &1\\\\0 &0 &0\\end{bmatrix}\\] This gives \\(x_1=0\\) and \\(x_2=-x_3\\) with one free variable \\(x_3\\). Again in parametric vector form, we can obtain \\[\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\end{bmatrix}=\\begin{bmatrix}0\\\\-x_3\\\\x_3\\end{bmatrix}=x_3\\begin{bmatrix}0\\\\-1\\\\1\\end{bmatrix}\\] A basis is \\(\\begin{Bmatrix}\\begin{bmatrix}0\\\\-1\\\\1\\end{bmatrix}\\end{Bmatrix}\\).
    • \n
  2. \n
  3. From the above solution we can directly write \\(P\\) and \\(D\\) below \\[P=\\begin{bmatrix}2 &-1 &0\\\\1 &0 &-1\\\\0 &1 &1\\end{bmatrix}\\quad\nD=\\begin{bmatrix}4 &0 &0\\\\0 &4 &0\\\\0 &0 &1\\end{bmatrix}\\]
  4. \n
\n\n
\n

Problem 10 (10 points)

\n

(4 points) (1) Find the eigenvalues and corresponding eigenvectors of the matrix \\[\\begin{bmatrix}-5 &1\\\\4 &-2\\end{bmatrix}\\]

\n

(2 points) (2) Find a general solution to the system of differential equations \\[\n\\begin{bmatrix}x'(t)\\\\y'(t)\\end{bmatrix}=\n\\begin{bmatrix}-5 &1\\\\4 &-2\\end{bmatrix}\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}\n\\]

\n

(4 points) (3) Let \\(\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}\\) be a particular soilution to the initial value problem \\[\n\\begin{bmatrix}x'(t)\\\\y'(t)\\end{bmatrix}=\n\\begin{bmatrix}-5 &1\\\\4 &-2\\end{bmatrix}\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix},\n\\begin{bmatrix}x(0)\\\\y(0)\\end{bmatrix}=\\begin{bmatrix}3\\\\7\\end{bmatrix}.\n\\] Find \\(x(1)+y(1)\\).

\n

Problem 10 Solution

\n
\n
    \n
  1. To find eigenvalues, write down the determinant as \\[\\begin{vmatrix}-5-\\lambda &1\\\\4 &-2-\\lambda\\end{vmatrix}=(\\lambda+6)(\\lambda+1)=0\\] So the eigenvalues are \\(\\lambda_1=-6\\) and \\(\\lambda_2=-1\\). Now follow the same method as Problem 9 solution to get eigenvectors for them.\n
      \n
    • For \\(\\lambda_1=-6\\), the new matrix is \\[\\begin{bmatrix}1 &1\\\\4 &4\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &1\\\\0 &0\\end{bmatrix}\\] The eigenvector is \\(\\begin{bmatrix}1\\\\-1\\end{bmatrix}\\).
    • \n
    • For \\(\\lambda_1=-1\\), the new matrix is \\[\\begin{bmatrix}-4 &1\\\\4 &-1\\end{bmatrix}\\sim\n \\begin{bmatrix}-4 &1\\\\0 &0\\end{bmatrix}\\] The eigenvector is \\(\\begin{bmatrix}1\\\\4\\end{bmatrix}\\).
    • \n
  2. \n
  3. The general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] So from this, since we already found out the eigenvalues and the corresponding eigenvectors, we can write down \\[\n\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}=c_1\\begin{bmatrix}1\\\\-1\\end{bmatrix}e^{-6t}+c_2\\begin{bmatrix}1\\\\4\\end{bmatrix}e^{-t}\n\\]
  4. \n
  5. Now apply the initial values of \\(x(0)\\) and \\(y(0)\\), here comes the following equations: \\[\\begin{align}\nc_1+c_2&=3\\\\\n-c_1+4c_2&=7\n\\end{align}\\] This gives \\(c_1=1\\) and \\(c_2=2\\). So \\(x(1)+y(1)=e^{-6}+2e^{-1}-e^{-6}+8e^{-1}=10e^{-1}\\).
  6. \n
\n\n
\n

Summary

\n

Here is the table listing the key knowledge points for each problem in this exam:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Problem #Points of KnowledgeBook Sections
1The Rank Theorem4.6 \"Rank\"
2Linear dependence, Invertible Matrix Theorem4.3 \"Linearly Independent Sets; Bases\", 4.6 \"Rank\"
3Eigenvectors and Eigenvalues5.1 \"Eigenvectors and Eigenvalues\"
4Vector Spaces and Subspaces4.1 \"Vector Spaces and Subspaces\"
5Eigenfunctions of the Differential Equation5.7 \"Applications to Differential Equations\"
6The Diagonalization Theorem, Diagonalizing Matrices5.3 \"Diagonalization\"
7Complex Eigenvalues and Eigenvectors5.5 \"Complex Eigenvalues\"
8Kernel and Range of a Linear Transformation4.2 \"Null Spaces, Column Spaces, and Linear Transformations\"
9Eigenvalues, Basis for Eigenspace, Diagonalizing Matrices5.1 \"Eigenvectors and Eigenvalues\", 5.3 \"Diagonalization\"
10Eigenvectors and Eigenvalues5.1 \"Eigenvectors and Eigenvalues\", 5.7 \"Applications to Differential Equations\"
\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Spring 2022 Final Exam Solutions","url":"/en/2024/04/18/Purdue-MA265-2022-Spring-Final/","content":"

Here comes the solution and analysis for Purdue MA 26500 Spring 2022 Final exam. This exam covers all topics from Chapter 1 (Linear Equations in Linear Algebra) to Chapter 7 Section 1 (Diagonalization of Symmetric Matrices).

\n

Introduction

\n

Purdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.

\n

Textbook and Study Guide

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.

\n
\n

Exam Information

\n

MA 26500 Final exam covers all the topics from Chapter 1 to Chapter 7 Sections 1 in the textbook. This is a two-hour comprehensive common final exam given during the final exam week. There are 25 multiple-choice questions on the final exam.

\n

Reference Links

\n\n

Spring 2022 Final Exam Solutions

\n

Problem 1

\n

\n

Problem 1 Solution

\n
\n

Start with the augmented matrix of the system, and do row reduction like the below

\n

\\[\n\\left[\\begin{array}{ccc|c}1&2&3&16\\\\2&0&-2&14\\\\3&2&1&3a\\end{array}\\right]\\sim\n\\left[\\begin{array}{ccc|c}1&2&3&16\\\\0&-4&-8&-18\\\\0&-4&-8&3a-48\\end{array}\\right]\\sim\n\\left[\\begin{array}{ccc|c}1&2&3&16\\\\0&-4&-8&-18\\\\0&0&0&3a-30\\end{array}\\right]\n\\]

\n

Clearly, this system of equations is consistent when \\(a=10\\). So the answer is B.

\n\n
\n

Problem 2

\n

\n

Problem 2 Solution

\n
\n

First review the properties of determinants:
\n>Let A be a square matrix.
\n> a. If a multiple of one row of \\(A\\) is added to another row to produce a matrix \\(B\\), then \\(\\det B =\\det A\\).
\nb. If two rows of \\(A\\) are interchanged to produce \\(B\\), then \\(\\det B=-\\det A\\).
\nc. If one row of A is multiplied by \\(k\\) to produce B, then \\(\\det B=k\\cdot\\det A\\).

\n

Also since \\(\\det A^T=\\det A\\), a row operation on \\(A^T\\) amounts to a column operation on \\(A\\). The above property is true for column operations as well.

\n

With these properties in mind, we can do the following

\n

\\[\\begin{align}\n\\begin{vmatrix}d&2a&g+d\\\\e&2b&h+e\\\\f&2c&i+f\\end{vmatrix}\n&=2\\times \\begin{vmatrix}d&a&g+d\\\\e&b&h+e\\\\f&c&i+f\\end{vmatrix}=\n 2\\times \\begin{vmatrix}d&a&g\\\\e&b&h\\\\f&c&i\\end{vmatrix}=\n 2\\times (-1)\\times \\begin{vmatrix}a&d&g\\\\b&e&h\\\\c&f&i\\end{vmatrix}\\\\\n&=(-2)\\times \\begin{vmatrix}a&b&c\\\\d&e&f\\\\g&h&i\\end{vmatrix}=(-2)\\times 1=-2\n\\end{align}\\]

\n

So the answer is A.

\n\n
\n

Problem 3

\n

\n

Problem 3 Solution

\n
\n

Denote \\(A=BCB^{-1}\\), it can be seen that \\[\\det A=\\det (BCB^{-1})=\\det B\\det C\\det B^{-1}=\\det (BB^{-1})\\det C=\\det C\\]

\n

Thus we can directly write down the determinant calculation process like below (applying row operations) \\[\n\\begin{vmatrix}1&2&3\\\\1&4&5\\\\-1&3&7\\end{vmatrix}=\n\\begin{vmatrix}1&2&3\\\\0&2&2\\\\0&5&10\\end{vmatrix}=\n1\\times (-1)^{1+1}\\begin{vmatrix}2&2\\\\5&10\\end{vmatrix}=\n1\\times (2\\times 10-2\\times 5)=10\n\\]

\n

So the answer is B.

\n\n
\n

Problem 4

\n

\n

Problem 4 Solution

\n
\n\n\n
\n

Problem 5

\n

\n

Problem 5 Solution

\n
\n\n\n
\n

Problem 6

\n

\n

Problem 6 Solution

\n
\n

Note the trace of a square matrix \\(A\\) is the sum of the diagonal entries in A and is denoted by tr \\(A\\).

\n

Remember the formula for inverse matrix \\[\nA^{-1}=\\frac{1}{\\det A}\\text{adj}\\;A=[b_{ij}]\\qquad\nb_{ij}=\\frac{C_{ji}}{\\det A}\\qquad C_{ji}=(-1)^{i+j}\\det A_{ji}\n\\] Where \\(\\text{adj}\\;A\\) is the adjugate of \\(A\\), \\(C_{ji}\\) is a cofactor of \\(A\\), and \\(A_{ji}\\) denotes the submatrix of \\(A\\) formed by deleting row \\(j\\) and column \\(i\\).

\n

Now we can find the answer step-by-step:

\n
    \n
  1. Calculate the determinant of \\(A\\) \\[\n\\begin{vmatrix}1&2&7\\\\1&3&12\\\\2&5&20\\end{vmatrix}=\n\\begin{vmatrix}1&2&7\\\\0&1&5\\\\0&1&6\\end{vmatrix}=\n\\begin{vmatrix}1&2&7\\\\0&1&5\\\\0&0&1\\end{vmatrix}=1\n\\]

  2. \n
  3. Calculate \\(b_{11}\\), \\(b_{22}\\), and \\(b_{33}\\) \\[\nb_{11}=\\frac{C_{11}}{1}=\\begin{vmatrix}3&12\\\\5&20\\end{vmatrix}=0\\\\\nb_{22}=\\frac{C_{22}}{1}=\\begin{vmatrix}1&7\\\\2&20\\end{vmatrix}=6\\\\\nb_{33}=\\frac{C_{33}}{1}=\\begin{vmatrix}1&2\\\\1&3\\end{vmatrix}=1\n\\]

  4. \n
  5. Get the trace of \\(A^{-1}\\) \\[\\text{tr}\\;A^{-1}=b_{11}+b_{22}+b_{33}=0+6+1=7\\]

  6. \n
\n

So the answer is C.

\n\n
\n

Problem 7

\n

\n

Problem 7 Solution

\n
\n

First do row reduction to get row echelon form of the matrix \\(A\\):

\n

\\[\\begin{align}\n&\\begin{bmatrix}1&2&2&10&3\\\\2&4&1&11&5\\\\3&6&2&18&1\\end{bmatrix}\\sim\n\\begin{bmatrix}1&2&2&10&3\\\\0&0&-3&-9&-1\\\\0&0&-4&-12&-8\\end{bmatrix}\\sim\n\\begin{bmatrix}1&2&2&10&3\\\\0&0&3&9&1\\\\0&0&1&3&2\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1&2&2&10&3\\\\0&0&3&9&1\\\\0&0&3&9&6\\end{bmatrix}\n\\sim\\begin{bmatrix}\\color{fuchsia}{1}&2&2&10&3\\\\0&0&\\color{fuchsia}{3}&9&1\\\\0&0&0&0&\\color{fuchsia}{5}\\end{bmatrix}\n\\end{align}\\]

\n

This shows that there are 3 pivot elements and 3 corresponding pivot columns (from the original matrix \\(A\\)) shown below

\n

\\[\\begin{Bmatrix}\n\\begin{bmatrix}1\\\\2\\\\3\\end{bmatrix},\n\\begin{bmatrix}2\\\\1\\\\2\\end{bmatrix},\n\\begin{bmatrix}3\\\\5\\\\1\\end{bmatrix}\n\\end{Bmatrix}\\]

\n

These columns form a basis for \\(\\text{Col}\\;A\\). Now look at the statements A and E.

\n

In the statement A, the first vector equals the sum of the first two pivot columns above. In the statement E, the third vector equals the sum of the last two pivot columns above. So both are TRUE.

\n

To check the statements B, C, and D, we need to find the basis for \\(\\text{Nul}\\;A\\). From the row echelon form, it can be deduced that with \\(x_2\\) and \\(x_4\\) as free variable \\[\\begin{align}\nx_5&=0\\\\x_3&=-3x_4\\\\x_1&=-2x_2-2x_3-10x_4=-2x_2-4x_4\n\\end{align}\\] This leads to \\[\n\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n\\begin{bmatrix}-2x_2-4x_4\\\\x_2\\\\-3x_4\\\\x_4\\\\0\\end{bmatrix}=\nx_2\\begin{bmatrix}-2\\\\1\\\\0\\\\0\\\\0\\end{bmatrix}+x_4\\begin{bmatrix}-4\\\\0\\\\-3\\\\1\\\\0\\end{bmatrix}\n\\]

\n

So the basis of \\(\\text{Nul}\\;A\\) is \\[\\begin{Bmatrix}\n\\begin{bmatrix}-2\\\\1\\\\0\\\\0\\\\0\\end{bmatrix},\n\\begin{bmatrix}-4\\\\0\\\\-3\\\\1\\\\0\\end{bmatrix}\n\\end{Bmatrix}\\]

\n

The statement B is TRUE because its first vector is the first column above scaled by 2, and its 2nd vector is just the 2nd column above scaled by -1.

\n

For statement D, its 1st vector is the same as the first column above, and the 2nd vector is just the sum of the two columns. It is TRUE as well.

\n

The statement B is FALSE since generating the 2nd vector with 3 and -2 coexisting is impossible.

\n

So the answer is C.

\n\n
\n

Problem 8

\n

\n

Problem 8 Solution

\n
\n\n\n
\n

Problem 9

\n

\n

Problem 9 Solution

\n
\n

To find the \\(\\text{Ker}(T)\\), need to find the set of \\(p(t)\\) such that \\(T(p(t))=0\\) \\[\nT(a_0+a_{1}t+a_{2}t^2)=a_{2}t^3=0 \\Rightarrow a_2=0\n\\] Thus \\(p(t)=a_0+a_{1}t\\), the basis is \\({1,t}\\).

\n

So the answer is A.

\n\n
\n

Problem 10

\n

\n

Problem 10 Solution

\n
\n\n\n
\n

Problem 11

\n

\n

Problem 11 Solution

\n
\n

The vector set can be regarded as a linear transformation, then we can do row reduction with it:

\n

\\[\n\\begin{bmatrix}1&1&1&1&1\\\\-1&1&2&0&-2\\\\1&1&1&1&3\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}{1}&1&1&1&1\\\\0&\\color{fuchsia}{2}&3&1&-1\\\\0&0&0&0&\\color{fuchsia}{2}\\end{bmatrix}\n\\] So there are 3 pivot entries and the rank is 3. The pivot columns below form a basis for \\(H\\). \\[\\begin{Bmatrix}\n\\begin{bmatrix}1\\\\-1\\\\1\\end{bmatrix},\n\\begin{bmatrix}1\\\\1\\\\1\\end{bmatrix},\n\\begin{bmatrix}1\\\\-2\\\\3\\end{bmatrix}\n\\end{Bmatrix}\\]

\n

A is wrong as it has only 2 vectors and the rank is 2.

\n

For B, C, and D, their 3rd vectors can be generated with the linear combination of the first two vectors. So their ranks are also 2.

\n

E is equivalent to the basis above. Its second vector can be generated like below \\[\n\\begin{bmatrix}1\\\\-1\\\\1\\end{bmatrix}+\\begin{bmatrix}1\\\\1\\\\1\\end{bmatrix}=\n\\begin{bmatrix}2\\\\0\\\\2\\end{bmatrix}=2\\times \\begin{bmatrix}1\\\\0\\\\1\\end{bmatrix}\n\\]

\n

So the answer is E.

\n\n
\n

Problem 12

\n

\n

Problem 12 Solution

\n
\n

Note this question asks which one is NOT in the subspace spanned by \\(\\pmb x\\) and \\(\\pmb y\\). A vector is in the subspace spanned by \\(\\pmb x\\) and \\(\\pmb y\\) if and only if it is a linear combination of \\(\\pmb x\\) and \\(\\pmb y\\). This also means that the augmented matrix \\([\\pmb x\\;\\pmb y \\mid \\pmb v]\\) has solutions.

\n

Let's try vector from A. \\[\n\\left[\\begin{array}{cc|c}2&1&4\\\\3&2&2\\\\1&1&1\\end{array}\\right]\\sim\n\\left[\\begin{array}{cc|c}2&1&4\\\\3&2&2\\\\2&2&2\\end{array}\\right]\\sim\n\\left[\\begin{array}{cc|c}2&1&4\\\\1&0&0\\\\0&1&-2\\end{array}\\right]\\sim\n\\left[\\begin{array}{cc|c}2&0&6\\\\1&0&0\\\\0&1&-2\\end{array}\\right]\\sim\n\\] This gives inconsistent results for \\(x_1\\). This vector is NOT a linear combination of \\(\\pmb x\\) and \\(\\pmb y\\). We do not need to continue here.

\n

So the answer is A.

\n\n
\n

Problem 13

\n

\n

Problem 13 Solution

\n
\n

For 2 radians counter-clockwise rotation, the transformation matrix is written as \\[A=\\begin{bmatrix}\\cos(2)&-\\sin(2)\\\\\\sin(2)&\\cos(2)\\end{bmatrix}\\] To find the eigenvalues of this \\(2\\times 2\\) matrix, need to solve the equation \\(\\det (A-\\lambda I)=0\\) \\[\n\\begin{vmatrix}\\cos(2)-\\lambda&\\sin(2)\\\\-\\sin(2)&\\cos(2)-\\lambda\\end{vmatrix}=\\lambda^2-2\\cos(2)+\\cos^2(2)+\\sin^2(2)=\\lambda^2-2\\cos(2)+1\n\\] Apply the quadratic formula, get the roots \\[\\lambda=\\frac{2\\cos(2)\\pm\\sqrt{4\\cos^2(2)-4}}{2}=\\cos(2)\\pm i\\sin(2)\\]

\n

So the answer is C.

\n\n
\n

Problem 14

\n

\n

Problem 14 Solution

\n
\n\n\n
\n

Problem 15

\n

\n

Problem 15 Solution

\n
\n\n\n
\n

Problem 16

\n

\n

Problem 16 Solution

\n
\n\n\n
\n

Problem 17

\n

\n

Problem 17 Solution

\n
\n\n\n
\n

Problem 18

\n

\n

Problem 18 Solution

\n
\n

Remember Problem 6 introduced the definition of trace, which is the sum of all diagonal entries of a matrix. Denote the \\(2\\times 2\\) as \\(A=\\begin{bmatrix}a&b\\\\c&d\\end{bmatrix}\\), then \\(\\text{tr}(A)=a+d=-2\\). Since \\(\\det A=11\\), it gives \\(ad-bc=11\\).

\n

With these in mind, we can do the eigenvalue calculation below \\[\n\\begin{vmatrix}a-\\lambda&b\\\\c&d-\\lambda\\end{vmatrix}=\\lambda^2-(a+d)\\lambda+ad-bc=\\lambda^2+2\\lambda+11=0\n\\] Apply the quadratic formula, get the roots \\[\\lambda=\\frac{-2\\pm\\sqrt{4-44}}{2}=-1\\pm i\\sqrt{10}\\]

\n
\n

Refer to the following table for the mapping from \\(2\\times 2\\) matrix eigenvalues to trajectories:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
EigenvaluesTrajectories
\\(\\lambda_1>0, \\lambda_2>0\\)Repeller/Source
\\(\\lambda_1<0, \\lambda_2<0\\)Attactor/Sink
\\(\\lambda_1<0, \\lambda_2>0\\)Saddle Point
\\(\\lambda = a\\pm bi, a>0\\)Spiral (outward) Point
\\(\\lambda = a\\pm bi, a<0\\)Spiral (inward) Point
\\(\\lambda = \\pm bi\\)Ellipses (circles if \\(b=1\\))
\n
\n

So the answer is C.

\n\n
\n

Problem 19

\n

\n

Problem 19 Solution

\n
\n\n\n
\n

Problem 20

\n

\n

Problem 20 Solution

\n
\n\n\n
\n

Problem 21

\n

\n

Problem 21 Solution

\n
\n\n\n
\n

Problem 22

\n

\n

Problem 22 Solution

\n
\n\n\n
\n

Problem 23

\n

\n

Problem 23 Solution

\n
\n\n\n
\n

Problem 24

\n

\n

Problem 24 Solution

\n
\n\n\n
\n

Problem 25

\n

\n

Problem 25 Solution

\n
\n\n\n
\n

Other MA265 Final Exam Solutions

\n\n

\nMA 265 Fall 2022 Final\n

\n\n

\nMA 265 Sprint 2023 Final\n

\n\n

\nMA 265 Fall 2019 Final\n

\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Spring 2022 Midterm II Solutions","url":"/en/2024/02/29/Purdue-MA265-2022-Spring-Midterm2/","content":"

Here comes the solution and analysis for Purdue MA 26500 Spring 2022 Midterm II. This second midterm covers topics in Chapter 4 (Vector Spaces) and Chapter 5 (Eigenvalues and Eigenvectors) of the textbook.

\n

Introduction

\n

Purdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.

\n

Textbook and Study Guide

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.

\n
\n

Exam Information

\n

MA 26500 midterm II covers the topics of Sections 4.1 – 5.7 in the textbook. It is usually scheduled at the beginning of the thirteenth week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.

\n

Based on the knowledge of linear equations and matrix algebra learned in the book chapters 1 and 2, Chapter 4 leads the student to a deep dive into the vector space framework. Chapter 5 introduces the important concepts of eigenvectors and eigenvalues. They are useful throughout pure and applied mathematics. Eigenvalues are also used to study differential equations and continuous dynamical systems, they provide critical information in engineering design,

\n

Reference Links

\n\n

Spring 2022 Midterm II Solutions

\n

Problem 1 (10 points)

\n

\n

Problem 1 Solution

\n
\n

A From the following \\[c_1(\\pmb u+\\pmb v)+c_2(\\pmb v+\\pmb w)+c_3\\pmb w=c_1\\pmb u+(c_1+c_2)\\pmb v+(c_2+c_3)\\pmb w\\] it can be concluded that if \\(\\pmb u\\), \\(\\pmb v\\), and \\(\\pmb w\\) are linearly independent, it is always true that \\(\\pmb u+\\pmb v\\), \\(\\pmb v+\\pmb w\\), and \\(\\pmb w\\) are linearly independent. So this statement is always true.

\n

B This is also true. If the number of vectors is greater than the number of entries (\\(n\\) here), the transformation matrix has more columns than rows. The column vectors are not linearly independent.

\n

C This is always true per the definition of basis and spanning set.

\n

D If the nullity of a \\(m\\times n\\) matrix \\(A\\) is zero, \\(rank A=n\\). This means there the column vectors form a linearly independent set, and there is one pivot in each column. However, this does not mean \\(A\\pmb x=\\pmb b\\) has a unique solution for every \\(\\pmb b\\). For example, see the following augmented matrix in row echelon form (after row reduction): \\[\n\\begin{bmatrix}1 &\\ast &\\ast &b_1\\\\0 &1 &\\ast &b_2\\\\0 &0 &1 &b_3\\\\0 &0 &0 &b_4\\end{bmatrix}\n\\] If \\(b_4\\) is not zero, the system is inconsistent and there is no solution. So this one is NOT always true.

\n

E This is always true since the rank of a \\(m\\times n\\) matirx is always in the range of \\([0, n]\\).

\n

So the answer is D.

\n\n
\n

Problem 2 (10 points)

\n

\n

Problem 2 Solution

\n
\n

Denote \\(3\\times 3\\) matrix as \\(A=\\begin{bmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\end{bmatrix}\\), then from the given condition we can get \\[\\begin{align}\n&\\begin{bmatrix}1 &0 &0\\\\0 &2 &0\\\\0 &0 &3\\end{bmatrix}\\begin{bmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\end{bmatrix}=\\begin{bmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\end{bmatrix}\\begin{bmatrix}1 &0 &0\\\\0 &2 &0\\\\0 &0 &3\\end{bmatrix}\\\\\n\\implies&\\begin{bmatrix}a &b &c\\\\2d &2e &2f\\\\3g &3h &3i\\end{bmatrix}=\\begin{bmatrix}a &2b &3c\\\\d &2e &3f\\\\g &2h &3i\\end{bmatrix}\\\\\n\\implies&A=\\begin{bmatrix}a &0 &0\\\\0 &2e &0\\\\0 &0 &3i\\end{bmatrix}=a\\begin{bmatrix}1 &0 &0\\\\0 &0 &0\\\\0 &0 &0\\end{bmatrix}+\n2e\\begin{bmatrix}0 &0 &0\\\\0 &1 &0\\\\0 &0 &0\\end{bmatrix}+\n3i\\begin{bmatrix}0 &0 &0\\\\0 &0 &0\\\\0 &0 &1\\end{bmatrix}\n\\end{align}\\]

\n

It can be seen that there are three basis vectors for this subspace and the dimension is 3. The answer is A.

\n

Notice the effects of left-multiplication and right-multiplication of a diagonal matrix.

\n\n
\n

Problem 3 (10 points)

\n

\n

Problem 3 Solution

\n
\n

From \\(\\det A-\\lambda I\\), it becomes \\[\\begin{align}\n\\begin{vmatrix}4-\\lambda &0 &0 &0\\\\-2 &-1-\\lambda &0 &0\\\\10 &-9 &6-\\lambda &a\\\\1 &5 &a &3-\\lambda\\end{vmatrix}\n&=(4-\\lambda)(-1-\\lambda)((6-\\lambda)(3-\\lambda)-a^2)\\\\\n&=(\\lambda-4)(\\lambda+1)(\\lambda^2-9\\lambda+18-a^2)\n\\end{align}\\]

\n

So if 2 is an eigenvalue for the above, the last multiplication item becomes \\((2^2-18+18-a^2)\\) that should be zero. So \\(a=\\pm 2\\).

\n

The answer is E.

\n\n
\n

Problem 4 (10 points)

\n

\n

Problem 4 Solution

\n
\n

(i) Referring to Theorem 4 in Section 5.2 \"The Characteristic Equation\" >If \\(n\\times n\\) matrices \\(A\\) and \\(B\\) are similar, then they have the same characteristic polynomial and hence the same eigenvalues (with the same multiplicities).

\n

So this statement must be TRUE.

\n

(ii) If the columns of \\(A\\) are linearly independent, \\(A\\pmb x=\\pmb 0\\) only has trivial solution and \\(A\\) is an invertible matrix. This also means \\(\\det A\\neq 0\\). From here, it must be TRUE that \\(\\det A-0 I\\neq 0\\). So 0 is NOT an eigenvalue of \\(A\\). This statement is FALSE.

\n

(iii) A matrix \\(A\\) is said to be diagonalizable if it is similar to a diagonal matrix, which means that there exists an invertible matrix \\(P\\) such that \\(P^{-1}AP\\) is a diagonal matrix. In other words, \\(A\\) is diagonalizable if it has a linearly independent set of eigenvectors that can form a basis for the vector space.

\n

However, the condition for diagonalizability does not require that all eigenvalues be nonzero. A matrix can be diagonalizable even if it has one or more zero eigenvalues. For example, consider the following matrix: \\[A=\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix}\n=\\begin{bmatrix}1 &0\\\\0 &1\\end{bmatrix}\\begin{bmatrix}1 &0\\\\0 &0\\end{bmatrix}\\begin{bmatrix}1 &0\\\\0 &1\\end{bmatrix}\\] This matrix has one nonzero eigenvalue (\\(λ = 1\\)) and one zero eigenvalue (\\(λ = 0\\)). However, it is diagonalizable with the identity matrix as \\(P\\) and \\(D=A\\).

\n

So this statement is FALSE.

\n

(iv) Similar matrices have the same eigenvalues (with the same multiplicities). Hence \\(-\\lambda\\) is also an eigenvalue of \\(B\\). Then we have \\(B\\pmb x=-\\lambda\\pmb x\\). From this, \\[\nBB\\pmb x=B(-\\lambda)\\pmb x=(-\\lambda)B\\pmb x=(-\\lambda)(-\\lambda)\\pmb x=\\lambda^2\\pmb x\n\\] So \\(\\lambda^2\\) is an eigenvalue of \\(B^2\\). Following the same deduction, we can prove that \\(\\lambda^4\\) is an eigenvalue of \\(B^4\\). This statement is TRUE.

\n

(v) Denote \\(A=PBP^{-1}\\). If \\(A\\) is diagonizible, then \\(A=QDQ^{-1}\\) for some diagonal matrix \\(D\\). Now we can also write down \\[B=P^{-1}AP=P^{-1}QDQ^{-1}P=(P^{-1}Q)D(P^{-1}Q)^{-1}\\] This proves that \\(B\\) is also diagonalizable. This statement is TRUE.

\n

Since statements (ii) and (iii) are FALSE and the rest are TRUE, the answer is D.

\n\n
\n

Problem 5 (10 points)

\n

\n

Problem 5 Solution

\n
\n

(i) Obviously \\(x=y=z=0\\) does not satisfy \\(x+2y+3z=1\\), this subset is NOT a subspace of \\(\\mathbb R^3\\).

\n

(ii) This subset is a subspace of \\(\\mathbb R^3\\) since it has all the three properties of subspace:

\n
    \n
  1. Be \\(x=y=z=0\\) satisfies \\(10x-2y=z\\), so the set includes the zero vector.
  2. \n
  3. Because \\(10(x_1+x_2)-2(y_1+y_2)=z_1+z_2\\), it is closed under vector addition.
  4. \n
  5. \\(10cx-2cy=cz\\), it is closed under scalar multiplication as well.
  6. \n
\n

(iii) Here \\(p(t)=a_0+a_1t+a_2t^2+a_3t^3\\) and \\(a_3\\neq 0\\). This set does not include zero polynomial. Besides, if \\(p_1(t)=t^3+t\\) and \\(p_2(t)=-t^3+t\\), then \\(p_1(t)+p_2(t)=2t\\). This result is not a polynomial of degree 3. So this subset is NOT closed under vector addition and is NOT a subspace of \\(\\mathbb P_3\\).

\n

(iv) The condition \\(p(2)=0\\) means \\(a_0+2a_1+4a_3+8a_3=0\\). It does include zero polynomial. It also satisfies the other two properties because \\[\\begin{align}\ncp(2)&=c(a_0+2a_1+4a_3+8a_3)=0\\\\\np_1(2)+p_2(2)&=(a_0+2a_1+4a_3+8a_3)+(b_0+2b_1+4b_3+8b_3)=0\n\\end{align}\\] So this set is indeed a subset of \\(\\mathbb P_3\\).

\n

Since we have (ii) and (iv) be our choices, the answer is A.

\n\n
\n

Problem 6 (10 points)

\n

\n

Problem 6 Solution

\n
\n

\\[\n\\begin{vmatrix}4-\\lambda &2\\\\3 &5-\\lambda\\end{vmatrix}=\\lambda^2-9\\lambda+20-6=(\\lambda-2)(\\lambda-7)\n\\]

\n

So there are two eigenvalues 2 and 7. Since both are positive, the origin is a repeller. The answer is B.

\n\n
\n

Problem 7 (10 points)

\n

\n

Problem 7 Solution

\n
\n

From Section 5.7 \"Applications to Differential Equations\", we learn that the general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] For a real matrix, complex eigenvalues and associated eigenvectors come in conjugate pairs. The real and imaginary parts of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) are (real) solutions of \\(\\pmb x'(t)=A\\pmb x(t)\\), because they are linear combinations of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) and \\(\\pmb{v}_2 e^{\\lambda_2 t}\\). (See the proof in \"Complex Eigenvalues\" of Section 5.7)

\n

Now use Euler's formula (\\(e^{ix}=\\cos x+i\\sin x\\)), we have \\[\\begin{align}\n\\pmb{v}_1 e^{\\lambda_1 t}\n&=e^{1+i}\\begin{bmatrix}1-2i\\\\3+4i\\end{bmatrix}\\\\\n&=e^t(\\cos t+i\\sin t)\\begin{bmatrix}1-2i\\\\3+4i\\end{bmatrix}\\\\\n&=e^t\\begin{bmatrix}\\cos t+2\\sin t+i(\\sin t-2\\cos t)\\\\3\\cos t-4\\sin t+i(3\\sin t+4\\cos t)\\end{bmatrix}\n\\end{align}\\] The general REAL solution is the linear combination of the REAL and IMAGINARY parts of the result above, it is \\[c_1 e^t\\begin{bmatrix}\\cos t+2\\sin t\\\\3\\cos t-4\\sin t\\end{bmatrix}+\nc_2 e^t\\begin{bmatrix}\\sin t-2\\cos t\\\\3\\sin t+4\\cos t\\end{bmatrix}\\]

\n

The answer is A.

\n\n
\n

Problem 8 (10 points)

\n

\n

Problem 8 Solution

\n
\n

(1) Since \\(p(t)=at^2+bt+c\\), its derivative is \\(p'(t)=2at+b\\). So we can have \\[\nT(at^2+bt+c)=\\begin{bmatrix}c &b\\\\a+b+c &2a+b\\end{bmatrix}\n\\]

\n

(2) From the result of (1) above, we can directly write down that \\(c=1\\) and \\(b=2\\). Then because \\(2a+b=4\\), \\(a=2\\). So \\(p(t)=t^2+2t+1\\).

\n

(3) Write down this transformation as the parametric vector form like below \\[\n\\begin{bmatrix}c &b\\\\a+b+c &2a+b\\end{bmatrix}=\na\\begin{bmatrix}0 &0\\\\1 &2\\end{bmatrix}+\nb\\begin{bmatrix}0 &1\\\\1 &1\\end{bmatrix}+\nc\\begin{bmatrix}1 &0\\\\1 &0\\end{bmatrix}\n\\] So a basis for the range of \\(T\\) is \\[\n\\begin{Bmatrix}\n\\begin{bmatrix}0 &0\\\\1 &2\\end{bmatrix},\n\\begin{bmatrix}0 &1\\\\1 &1\\end{bmatrix},\n\\begin{bmatrix}1 &0\\\\1 &0\\end{bmatrix}\n\\end{Bmatrix}\n\\]

\n\n
\n

Problem 9 (10 points)

\n

\n

Problem 9 Solution

\n
\n

(1) First find all the eigenvalues using \\(\\det A-\\lambda I=0\\) \\[\n\\begin{align}\n\\begin{vmatrix}2-\\lambda &0 &0\\\\1 &5-\\lambda &1\\\\-1 &-3 &1-\\lambda\\end{vmatrix}&=(2-\\lambda)\\begin{vmatrix}5-\\lambda &1\\\\-3 &1\\lambda\\end{vmatrix}\\\\\n&=(2-\\lambda)(\\lambda^2-6\\lambda+5+3)\\\\\n&=(2-\\lambda)(\\lambda-2)(\\lambda-4)\n\\end{align}\n\\] So there are two eigenvalues 2 with multiplicity and 4.

\n

Now find out the eigenvector(s) for each eigenvalue

\n
    \n
  • For \\(\\lambda_1=\\lambda_2=2\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}0 &0 &0\\\\1 &3 &1\\\\-1 &-3 &-1\\end{bmatrix}\\sim\n\\begin{bmatrix}0 &0 &0\\\\1 &3 &1\\\\0 &0 &0\\end{bmatrix}\n\\] Convert this result to a parametric vector form with two free variables \\(x_2\\) and \\(x_3\\) \\[\n\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\end{bmatrix}=\n\\begin{bmatrix}-3x_2-x_3\\\\x_2\\\\x_3\\end{bmatrix}=\nx_2\\begin{bmatrix}-3\\\\1\\\\0\\end{bmatrix}+x_3\\begin{bmatrix}-1\\\\0\\\\1\\end{bmatrix}\n\\] So the basis for the eigenspace is \\(\\begin{Bmatrix}\\begin{bmatrix}-3\\\\1\\\\0\\end{bmatrix},\\begin{bmatrix}-1\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\\).

  • \n
  • For \\(\\lambda_3=4\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}-2 &0 &0\\\\1 &1 &1\\\\-1 &-3 &-3\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0\\\\0 &1 &1\\\\0 &-2 &-2\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0\\\\0 &1 &1\\\\0 &0 &0\\end{bmatrix}\n\\] This ends up with \\(x_1=0\\) and \\(x_2=-x_3\\). So the eigenvector is \\(\\begin{bmatrix}0\\\\-1\\\\1\\end{bmatrix}\\) or \\(\\begin{bmatrix}0\\\\1\\\\-1\\end{bmatrix}\\). The basis for the corresponding eigenspace is \\(\\begin{Bmatrix}\\begin{bmatrix}0\\\\-1\\\\1\\end{bmatrix}\\end{Bmatrix}\\) or \\(\\begin{Bmatrix}\\begin{bmatrix}0\\\\1\\\\-1\\end{bmatrix}\\end{Bmatrix}\\).

  • \n
\n

(2) From the answers of (1), we can directly write down \\(P\\) and \\(D\\) as \\[\nP=\\begin{bmatrix}-3 &-1 &0\\\\1 &0 &-1\\\\0 &1 &1\\end{bmatrix},\\;\nD=\\begin{bmatrix}2 &0 &0\\\\0 &2 &0\\\\0 &0 &4\\end{bmatrix}\n\\]

\n\n
\n

Problem 10 (10 points)

\n

\n

Problem 10 Solution

\n
\n

(1) First find the eigenvalues using \\(\\det A-\\lambda I=0\\) \\[\n\\begin{align}\n\\begin{vmatrix}9-\\lambda &5\\\\-6 &-2-\\lambda\\end{vmatrix}\n&=\\lambda^2-7\\lambda-18-(-5)\\cdot 6\\\\\n&=\\lambda^2-7\\lambda+12\\\\\n&=(\\lambda-3)(\\lambda-4)\n\\end{align}\n\\] So there are two eigenvalues 3 and 4.

\n
    \n
  • For \\(\\lambda_1=3\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}6 &5\\\\-6 &5\\end{bmatrix}\\sim\n\\begin{bmatrix}6 &5\\\\0 &0\\end{bmatrix}\n\\] So the eigenvector can be \\(\\begin{bmatrix}-5\\\\6\\end{bmatrix}\\).

  • \n
  • Likewise, for \\(\\lambda_2=4\\), the matrix \\(\\det A-\\lambda I\\) becomes \\[\n\\begin{bmatrix}5 &5\\\\-6 &-6\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &1\\\\0 &0\\end{bmatrix}\n\\] So the eigenvector can be \\(\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\).

  • \n
\n

(2) With the eigenvalues and corresponding eigenvectors known, we can apply them to the general solution formula \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] So the answer is \\[\n\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}=c_1\\begin{bmatrix}-5\\\\6\\end{bmatrix}e^{3t}+c_2\\begin{bmatrix}-1\\\\1\\end{bmatrix}e^{4t}\n\\]

\n

(3) Apply the initial values of \\(x(0)\\) and \\(y(0)\\), here comes the following equations: \\[\\begin{align}\n-5c_1-c_2&=1\\\\\n6c_1+c_2&=0\n\\end{align}\\] This gives \\(c_1=1\\) and \\(c_2=-6\\). So \\(x(1)+y(1)=-5e^{3}+6e^4+6e^3-6e^4=e^3\\).

\n\n
\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Fall 2023 Midterm I Solutions","url":"/en/2024/01/28/Purdue-MA265-2023-Fall-Midterm1/","content":"

This is the 3rd study notes post for the college linear algebra course. Here is the review of Purdue MA 26500 Fall 2023 midterm I. I provide solutions to all exam questions as well as concise explanations.

\n

There is hardly any theory which is more elementary [than linear algebra], in spite of the fact that generations of professors and textbook writers have obscured its simplicity by preposterous calculations with matrices.
Jean Dieudonné (1906~1992, French mathematician, notable for research in abstract algebra, algebraic geometry, and functional analysis.)

\n
\n

Introduction

\n

Purdue University Department of Mathematics provides an introductory-level linear algebra course MA 26500 every semester. Undergraduate students of science and engineering majors taking this course would gain a good mathematical foundation for their advanced studies in machine learning, computer graphics, control theory, etc.

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.

\n
\n

MA 26500 midterm I covers the topics in Sections 1.1 – 3.3 of the textbook. It is usually scheduled at the beginning of the seventh week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.

\n

Here are a few extra reference links for Purdue MA 26500:

\n\n

Fall 2023 Midterm I Solutions

\n

Problem 1 (10 points)

\n

\n

Problem 1 Solution

\n
\n

Because \\(C=B^{-1}A\\), we can left-multiply both sides by \\(B\\) and obtain \\(BC=BB^{-1}A=A\\). So \\[\n\\begin{bmatrix}0 & 1\\\\1 & 5\\\\\\end{bmatrix}\n\\begin{bmatrix}a & b\\\\c & d\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 1\\\\3 & 2\\\\\\end{bmatrix}\n\\] Further, compute matrix multiplication at the left side \\[\n\\begin{bmatrix}c &d\\\\a+5c &b+5d\\\\\\end{bmatrix}=\n\\begin{bmatrix}1 & 1\\\\3 & 2\\\\\\end{bmatrix}\n\\] From here we can directly get \\(c=d=1\\), then \\(a=-2\\) and \\(b=-3\\). This leads to \\(a+b+c+d=-3\\).

\n

The answer is A.

\n\n
\n

Problem 2 (10 points)

\n

\n

Problem 2 Solution

\n
\n

The reduced row echelon form has the same number of pivots as the original matrix. And the rank of a matrix \\(A\\) is just the number of pivot columns in \\(A\\). From these, we can deduce statement (iii) is true.

\n

Per the Rank Theorem (rank \\(A\\) + dim Nul \\(A\\) = \\(n\\)), since \\(\\mathrm{Rank}(A)=\\mathrm{Rank}(R)\\), we obtain \\(\\mathrm{Nul}(A)=\\mathrm{Nul}(R)\\). So statement (i) is true as well.

\n

For a square matrix \\(A\\), suppose that transforming \\(A\\) to a matrix in reduced row-echelon form using elementary row operations \\(E_kE_{k−1}⋯E_1A=R\\). Taking the determinants of both sides, we get \\(\\det E_kE_{k−1}⋯E_1A=\\det R\\). Now, using the fact that the determinant of a product of matrices is the same as the product of the determinants of the matrices, we get that \\[\\det A=\\frac{\\det R}{\\det E_1⋯\\det E_k}\\]

\n

According to the description in the \"Proofs of Theorems 3 and 6\" part in Section 3.2 Properties of Determinants, it is proven that \\(\\det E\\) would be either 1, -1, or a scalar. Taking all these into consideration, if \\(\\det R\\) is zero, \\(\\det A\\) must be zero. Statement (v) is true.

\n

📝Notes:The reduced row echelon form of a square matrix is either the identity matrix or contains a row of 0's. Hence, \\(\\det R\\) is either 1 or 0.

\n

Now look back at statement (ii), the column space of the matrix \\(A\\) is not necessarily equal to the column space of \\(R\\), because the reduced row echelon form could contain a row of 0's. In such a case, the spans of these two column spaces are different.

\n

For the same reason, we can conclude that the statement (iv) is false. Referring to Theorem 4 in Section 1.4 The Matrix Operation \\(A\\pmb x=\\pmb b\\) (check the \"Common Errors and Warnings\" in the end), \"For each \\(\\pmb b\\) in \\(\\pmb R^m\\), the equation \\(A\\pmb x=\\pmb b\\) has a solution\" is true if and only if \\(A\\) has a pivot position in every row (not column).

\n

The answer is A.

\n\n
\n

Problem 3 (10 points)

\n

\n

Problem 3 Solution

\n
\n

First, we can do row reduction to obtain the row echelon form of the standard matrix \\[\\begin{align}\n&\\begin{bmatrix}1 &a &a+1\\\\2 &a+2 &a-1\\\\2-a &0 &0\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &a &a+1\\\\0 &-a+2 &-a-3\\\\2-a &0 &0\\\\\\end{bmatrix}\\sim\\\\\n\\sim&\\begin{bmatrix}1 &a &a+1\\\\0 &-a+2 &-a-3\\\\0 &a(a-2) &(a+1)(a-2)\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &a &a+1\\\\0 &-a+2 &-a-3\\\\0 &0 &-4a-2\\\\\\end{bmatrix}\n\\end{align}\\]

\n

If \\(a=2\\), the 2nd column is a multiple of the 1st column, so the columns of \\(A\\) are not linearly independent, then the transformation would not be one-to-one (Check Theorem 12 of Section 1.9 The Matrix of a Linear Transformation).

\n

Moreover, if \\(a=-\\frac{1}{2}\\), the entries of the last row are all 0s. In such case, matrix \\(A\\) has only two pivots and \\(A\\pmb x=\\pmb 0\\) has non-trivial solutions, \\(L\\) is not one-to-one (See Theorem 11 of Section 1.9 The Matrix of a Linear Transformation).

\n

So the answer is C.

\n\n
\n

Problem 4 (10 points)

\n

\n

Problem 4 Solution

\n
\n

Statement A is wrong as none of these 3 vectors is a linear combination of the other two. They form a linearly independent set.

\n

Statement B is wrong as we need 4 linearly independent vectors to span \\(\\mathbb R^4\\).

\n

Statements C and D are also wrong because B is wrong. Not all vectors in \\(\\mathbb R^4\\) can be generated with a linear combination of these 3 vectors, and \\(A\\pmb x=\\pmb b\\) might have no solution.

\n

Statements E is correct. It has a unique but trivial solution. Quoted from the textbook Section 1.7 Linear Independence:

\n
\n

The columns of a matrix \\(A\\) are linearly independent if and only if the equation \\(A\\pmb x=\\pmb 0\\) has only the trivial solution.

\n
\n

So the answer is E.

\n\n
\n

Problem 5 (10 points)

\n

\n

Problem 5 Solution

\n
\n

From the given condition, we know that \\(A\\) is a \\(m\\times n\\) matrix. So statement A is wrong.

\n

Statement B is not necessarily true since \\(\\pmb b\\) could be outside of the range but still in the \\(\\mathbb R^m\\) as the codomain of \\(T\\). Statement E is also not true for the same reason.

\n

Statement D is wrong. Since \\(m\\) is the row number of the matrix \\(A\\), rank \\(A=m\\) just means the number of pivots is equal to the row number. To have the column linearly independent, we need the pivot number to be the same as the column number.

\n

Now we have only statement C left. If \\(m<n\\), the column vector set is linearly dependent. But \\(T\\) is one-to-one if and only if the columns of \\(A\\) are linearly independent. So \\(m<n\\) cannot be true.

\n

The answer is C.

\n\n
\n

Problem 6 (10 points)

\n

\n

Problem 6 Solution

\n
\n

This is to solve the following equation system: \\[\n\\begin{bmatrix}2 &3\\\\1 &-1\\\\5 &4\\\\\\end{bmatrix}\n\\begin{bmatrix}x_1\\\\x_2\\\\\\end{bmatrix}=\n\\begin{bmatrix}1\\\\3\\\\6\\\\\\end{bmatrix}\n\\] Let's do the row reduction with the augmented matrix \\[\n\\begin{bmatrix}2 &3 &1\\\\1 &-1 &3\\\\5 &4 &6\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-1 &3\\\\2 &3 &1\\\\5 &4 &6\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-1 &3\\\\0 &5 &-5\\\\0 &9 &-9\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-1 &3\\\\0 &1 &-1\\\\0 &0 &0\\\\\\end{bmatrix}\n\\]

\n

This yields the unique solution \\(x_1=2\\) and \\(x_2=-1\\). So the answer is B.

\n\n
\n

Problem 7 (10 points)

\n

\n

Problem 7 Solution

\n
\n

First, we can exclude E as it has a zero vector, and a vector set including a zero vector is always linearly dependent.

\n

C has its column 2 equal to 2 times column 1. It is not linearly independent.

\n

A is also wrong. It is easy to see that column 3 is equal to 2 times column 1 minus column 2.

\n

B has zeros in row 3 of all four vectors. So all the vectors have only 3 valid entries. But we have 4 vectors. Referring to Theorem 8 of Section 1.7 Linear Independence, this is equivalent to the case that 4 vectors are all in 3D space. So there must be one vector that is a linear combination of the other 3. B is not the right answer.

\n

D can be converted to the vector set \\[\\begin{Bmatrix}\n\\begin{bmatrix}1\\\\1\\\\0\\end{bmatrix},\n\\begin{bmatrix}0\\\\1\\\\1\\end{bmatrix},\n\\begin{bmatrix}1\\\\0\\\\0\\end{bmatrix}\n\\end{Bmatrix}\\] This is a linear independent vector set since we cannot get any column by linearly combining the other two.

\n

So the answer is D.

\n\n
\n

Problem 8 (10 points)

\n

\n

Problem 8 Solution

\n
\n
    \n
  1. Start with the augmented matrix and do row reduction \\[\n\\begin{bmatrix}1 &1 &a &1\\\\0 &1 &a^2-2 &a\\\\3 &2 &2 &3\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &1 &a &1\\\\0 &1 &a^2-2 &a\\\\0 &-1 &2-3a &0\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &1 &a &1\\\\0 &1 &a^2-2 &a\\\\0 &0 &a(a-3) &a\\\\\\end{bmatrix}\n\\]

  2. \n
  3. Apparently if \\(a=0\\), the last row has all zero entries, the system has one free variable and there are an infinite number of solutions.

  4. \n
  5. If \\(a=3\\), the last row indicates \\(0=3\\), the system is inconsistent and has no solution.

  6. \n
  7. If \\(a\\) is neither 3 nor 0, the row echelon form shows three pivots, thus the system has a unique solution.

  8. \n
\n\n
\n

Problem 9 (10 points)

\n

\n

Problem 9 Solution

\n
\n
    \n
  1. The sequence of row reduction to get the reduced row echelon form is shown below \\[\\begin{align}\n&\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\2 &0 &-3 &-4 &5\\\\5 &0 &-6 &-1 &14\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\0 &0 &-1 &0 &-1\\\\0 &0 &-1 &0 &-1\\\\\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\0 &0 &-1 &0 &-1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &-2 &3\\\\0 &0 &1 &0 &1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &0 &-2 &4\\\\0 &0 &1 &0 &1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\n\\end{align}\\]

  2. \n
  3. From the reduced row echelon form, we can see that there are two pivots and three free variables \\(x_2\\), \\(x_4\\), and \\(x_5\\). So the system \\(A\\pmb x=\\pmb 0\\) becomes \\[\\begin{align}\nx_1-2x_4+4x_5&=0\\\\\nx_3+x_5&=0\n\\end{align}\\]

  4. \n
\n

Now write the solution in parametric vector form. The general solution is \\(x_1=2x_4-4x_5\\), \\(x_3=-x_5\\). This can be written as \\[\n \\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n \\begin{bmatrix}2x_4-4x_5\\\\x_2\\\\-x_5\\\\x_4\\\\x_5\\end{bmatrix}=\n x_2\\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix}+\n x_4\\begin{bmatrix}2\\\\0\\\\0\\\\1\\\\0\\end{bmatrix}+\n x_5\\begin{bmatrix}-4\\\\0\\\\-1\\\\0\\\\1\\end{bmatrix}\n \\] So the basis for Nul \\(A\\) is \\[\\begin{Bmatrix}\n \\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix},\n \\begin{bmatrix}2\\\\0\\\\0\\\\1\\\\0\\end{bmatrix},\n \\begin{bmatrix}-4\\\\0\\\\-1\\\\0\\\\1\\end{bmatrix}\n \\end{Bmatrix}\\]

\n\n
\n

Problem 10 (10 points)

\n

\n

Problem 10 Solution

\n
\n
    \n
  1. For computing the determinant of matrix \\(A\\) with the 1st column cofactor expansion, note that the only nonzero entry in column 1 is \\(a_{1,4}=2\\), so we have \\[\\begin{align}\n\\det A&=(-1)^{1+4}\\cdot 2\\cdot\\begin{vmatrix}1 &2 &3\\\\0 &\\color{fuchsia}3 &0\\\\1 &1 &1\\end{vmatrix}\\\\\n &=(-2)\\cdot 3\\begin{vmatrix}1 &3\\\\1 &1\\end{vmatrix}=(-6)\\cdot(-2)=12\n\\end{align}\\]

  2. \n
  3. From the adjugate of \\(A\\), we deduce the formula

  4. \n
\n

\\[\\begin{align}\nb_{3,2}&=\\frac{C_{2,3}}{\\det A}=\\frac{1}{12}\\cdot(-1)^{2+3}\\begin{vmatrix}0 &1 &3\\\\0 &1 &1\\\\\\color{fuchsia}2 &0 &1\\end{vmatrix}\\\\\n&=\\frac{-1}{12}\\cdot(-1)^{3+1}\\cdot 2\\begin{vmatrix}1 &3\\\\1 &1\\end{vmatrix}=\\frac{1}{3}\n\\end{align}\\]

\n\n
\n

Exam Summary

\n

Here is the table listing the key knowledge points for each problem in this exam:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Problem #Points of Knowledge
1Matrix Multiplications, Inverse Matrix
2Column Space, Rank, Nul Space, Determinant, Pivot, Linear System Consistency
3Linear Transformation, One-to-One Mapping
4Linear Dependency, Vector Set Span \\(\\mathbb R^n\\), Unique Solution
5Linear Transformation, One-to-One Mapping, Rank, Column Linear Independency, Vector Set Span \\(\\mathbb R^n\\)
6Basis of Span \\({v_1, v_2}\\)
7Linear Independency Vector Set
8Row Echelon Form, Augmented Matrix, Linear System Solution Set and Consistency
9Reduced Row Echelon Form, Basis for the Null Space
10Determinant, Cofactor Expansion, Inverse Matrix, The Adjugate of Matrix
\n

As can be seen, it has a good coverage of the topics of the specified sections from the textbook. Students should carefully review those to prepare for this and similar exams.

\n

Common Errors and Warnings

\n

Here are a few warnings collected from the textbook. It is highly recommended that students preparing for the MA 265 Midterm I exam review these carefully to identify common errors and know how to prevent them in the test.

\n

The Matrix Equation

\n

\n

\n

Solution Sets of Linear System

\n

\n

\n

Linear Independence

\n

\n

\n

Matrix Operations

\n

\n

Subspace of \\(\\mathbb R^N\\)

\n

\n

Properties of Determinants

\n

\n

\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Spring 2023 Midterm I Solutions","url":"/en/2024/01/23/Purdue-MA265-2023-Spring-Midterm1/","content":"

This is the 2nd study notes post for the college linear algebra course. Here is the review of Purdue MA 26500 Spring 2023 midterm I. I provide solutions to all exam questions as well as concise explanations.

\n

Matrices act. They don't just sit there.
Gilbert Strang (American mathematician known for his contributions to finite element theory, the calculus of variations, wavelet analysis and linear algebra.)

\n
\n

Introduction

\n

Purdue University Department of Mathematics provides an introductory-level linear algebra course MA 26500 every semester. Undergraduate students of science and engineering majors taking this course would gain a good mathematical foundation for their advanced studies in machine learning, computer graphics, control theory, etc.

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.

\n
\n

MA 26500 midterm I covers the topics of Sections 1.1 – 3.3 in the textbook. It is usually scheduled at the beginning of the seventh week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.

\n

Here are a few extra reference links for Purdue MA 26500:

\n\n

Spring 2023 Midterm I Solutions

\n

Problem 1 (10 points)

\n

\n

Problem 1 Solution

\n
\n

Referring to Section 3.2 Property of Determinants, we can do row and column operations to efficiently find the determinant of the given matrix.

\n

\\[\\begin{align}\n\\begin{vmatrix}a &b &3c\\\\g &h &3i\\\\d+2a &e+2b &3f+6c\\\\\\end{vmatrix}&=(-1)\\cdot\\begin{vmatrix}a &b &3c\\\\d+2a &e+2b &3f+6c\\\\g &h &3i\\\\\\end{vmatrix}\\\\\n&=(-1)\\cdot\\begin{vmatrix}a &b &3c\\\\d &e &3f\\\\g &h &3i\\\\\\end{vmatrix}=\n(-1)\\cdot3\\begin{vmatrix}a &b &c\\\\d &e &f\\\\g &h &i\\\\\\end{vmatrix}\\\\\n&=-3\\cdot 2=-6\n\\end{align}\\]

\n

The exact sequence of the operations are

\n
    \n
  1. An interchange of rows 2 and 3 reverses the sign of the determinant.
  2. \n
  3. Adding -2 times row 1 to row 2 does not change the determinant.
  4. \n
  5. Factoring out a common multiple of column 3.
  6. \n
  7. Applying the known result of det \\(A\\).
  8. \n
\n

So the answer is B.

\n\n
\n

Problem 2 (10 points)

\n

\n

Problem 2 Solution

\n
\n

This problem tests the students' knowledge of rank and dimension. Referring to Section 2.9 Dimension and Rank, we know the following important points:

\n
\n
    \n
  1. Since the pivot columns of \\(A\\) form a basis for Col \\(A\\), the rank of \\(A\\) is just the number of pivot columns in \\(A\\).
  2. \n
  3. If a matrix \\(A\\) has \\(n\\) columns, then rank \\(A\\) + dim Nul \\(A\\) = \\(n\\).
  4. \n
\n
\n

To find out the number of pivot columns in \\(A\\), we can do elementary row operations to obtain the Row Echelon Form of matrix \\(A\\).

\n

\\[\\begin{align}\n&\\begin{bmatrix}1 &2 &2 &5 &0\\\\-2 &0 &-2 &2 &-4\\\\3 &4 &-1 &9 &2\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &2 &2 &5 &0\\\\0 &4 &-4 &12 &-4\\\\0 &-2 &2 &-6 &2\\\\\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &2 &2 &5 &0\\\\0 &1 &-1 &3 &-1\\\\0 &1 &-1 &3 &-1\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}{1} &2 &2 &5 &0\\\\0 &\\color{fuchsia}{1} &-1 &3 &-1\\\\0 &0 &0 &0 &0\\\\\\end{bmatrix}\n\\end{align}\\]

\n

Now it is clear that this matrix has two pivot columns, thus rank \\(A\\) is 2, and dim Nul \\(A\\) is \\(5-2=3\\).

\n

Since \\(5a-3b=5\\times 2-3\\times 3=1\\), the answer is A.

\n\n
\n

Problem 3 (10 points)

\n

\n

Problem 3 Solution

\n
\n

For such linear transformation \\(T:\\mathbb R^3\\to\\mathbb R^3\\), onto means for each \\(\\pmb b\\) in the codomain \\(\\mathbb R^{3}\\), there exists at least one solution of \\(T(\\pmb x)=\\pmb b\\).

\n

Let's do row reduction first to see

\n

\\[\\begin{align}\n&\\begin{bmatrix}1 &t &2\\\\3 &3 &t-5\\\\2 &0 &0\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\3 &3 &t-5\\\\1 &t &2\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\1 &1 &\\frac{t-5}{3}\\\\0 &t &2\\\\\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &0 &0\\\\0 &1 &\\frac{t-5}{3}\\\\0 &t &2\\\\\\end{bmatrix}\\sim\n \\begin{bmatrix}1 &0 &0\\\\0 &1 &\\frac{t-5}{3}\\\\0 &0 &2-\\frac{(t-5)t}{3}\\\\\\end{bmatrix}\n\\end{align}\\]

\n

Now inspect the entry of row 3 and column 3, it can be factorized as \\(\\frac{(6-t)(1+t)}{3}\\). If \\(t\\) is 6 or -1, this entry becomes 0. In such cases, for a nonzero \\(b_{3}\\) of \\(\\pmb b\\) in \\(\\mathbb R^{3}\\), there would be no solution at all.

\n

So to make this linear transformation onto \\(\\mathbb R^{3}\\), \\(t\\) cannot be 6 or -1. The answer is E.

\n\n
\n

Problem 4 (10 points)

\n

\n

Problem 4 Solution

\n
\n

Let's inspect the statements one by one.

\n

For (i), from Section 1.7 Linear Independence, because \\(A\\pmb x=\\pmb 0\\) has only a trivial solution, the columns of the matrix \\(A\\) are linearly independent. So there should be at most one solution for these column vectors to combine and obtain, this statement is true.

\n

Statement (ii) is also true. If \\(m<n\\), according to Theorem 8 of Section 1.7, the set of column vectors is linearly dependent, etc a \\(2\\times 3\\) matrix (see Example 5 of Section 1.7). Then \\(A\\pmb x=\\pmb 0\\) has a nontrivial solution. Now referring to Theorem 11 of Section 1.9, this linear transformation of matrix \\(A\\) is NOT one-to-one.

\n

Thinking of the case \\(3\\times 2\\) for the linear transformation \\(T: \\mathbb R^2\\to\\mathbb R^3\\), we can get one-to-one mapping. But for \\(T: \\mathbb R^3\\to\\mathbb R^2\\), there could be more than 1 point in 3D space mapping to a 2D point. It is not one-to-one.

\n

For (iii), certainly this is not true. A simple example can be a \\(3\\times 2\\) matrix like below \\[\\begin{bmatrix}1 &0\\\\1 &1\\\\0 &1\\\\\\end{bmatrix}\\] The two columns above are NOT linearly dependent.

\n

Statement (iv) is true as this is the exact case described by Theorem 4 (c) and (d) in Section 1.4.

\n

The answer is D.

\n\n
\n

Problem 5 (10 points)

\n

\n

Problem 5 Solution

\n
\n

From the given conditions, we know that the columns of \\(A\\) form a linearly dependent set. Equivalently this means \\(A\\) is not invertible and \\(A\\pmb x=\\pmb 0\\) has two nontrivial solutions \\[\\begin{align}\nA\\pmb x&=[\\pmb a_{1}\\,\\pmb a_{2}\\,\\pmb a_{3}\\,\\pmb a_{4}\\,\\pmb a_{5}]\\begin{bmatrix}5\\\\1\\\\-6\\\\-2\\\\0\\end{bmatrix}=\\pmb 0\\\\\nA\\pmb x&=[\\pmb a_{1}\\,\\pmb a_{2}\\,\\pmb a_{3}\\,\\pmb a_{4}\\,\\pmb a_{5}]\\begin{bmatrix}0\\\\2\\\\-7\\\\1\\\\3\\end{bmatrix}=\\pmb 0\\\\\n\\end{align}\\] So Statement E is false. Moveover, a noninvertible \\(A\\) has \\(\\det A = 0\\). The statement A is false too.

\n

The two nontrivial solutions for \\(A\\pmb x=\\pmb 0\\) are \\([5\\,\\,1\\,\\,-6\\,\\,-2\\,\\,0]^T\\) and \\([0\\,\\,2\\,\\,-7\\,\\,1\\,\\,-3]^T\\). As they are also linear independent as one is not a multiple of the other, they should be in the basis for Nul \\(A\\). But we are not sure if there are also other vectors in the basis. We can only deduce that dim Nul \\(A\\) is at least 2. From this, we decide that statement B is false.

\n

Again because rank \\(A\\) + dim Nul \\(A\\) = \\(5\\), and dim Nul \\(A\\) is greater than or equal to 2, rank \\(A\\) must be less than or equal to 3. Statement C is true.

\n

Statement D is not true either, since \\([1\\,\\,2\\,\\,-7\\,\\,1\\,\\,-3]^T\\) is not a linear combination of \\([5\\,\\,1\\,\\,-6\\,\\,-2\\,\\,0]^T\\) and \\([0\\,\\,2\\,\\,-7\\,\\,1\\,\\,-3]^T\\).

\n

So the answer is C.

\n\n
\n

Problem 6 (10 points)

\n

\n

Problem 6 Solution

\n
\n

Denote the adjugate of \\(A\\) as \\(B=\\{b_{ij}\\}\\), then \\(b_{ij}=C_{ji}\\), where \\(C_{ji}\\) is the cofactor of \\(A\\). Compute two non-corner entries of \\(B\\) below \\[\\begin{align}\nb_{12}&=C_{21}=(-1)^{2+1}\\begin{vmatrix}0 &-1\\\\1 &-1\\end{vmatrix}=-1\\\\\nb_{21}&=C_{12}=(-1)^{1+2}\\begin{vmatrix}-5 &-1\\\\3 &-1\\end{vmatrix}=-8\n\\end{align}\\]

\n

So the answer is C.

\n\n
\n

Problem 7 (10 points)

\n

\n

Problem 7 Solution

\n
\n

We need a set of 4 linearly independent vectors to span \\(\\mathbb R^4\\).

\n

Answer A contains the zero vector, thus the set is not linearly independent.

\n

Answer E contains only 3 vectors, not enough as the basis of \\(\\mathbb R^4\\).

\n

Answer D column 3 is 2 times column 2, and column 5 is equal to column 2 and column 4. So it has only 3 linearly independent vectors. Still not enough

\n

Answer C is also not correct. If we scale 1/3 to column 1, and then add it with columns 2 and 3 altogether, it results in column 4. So only 3 linearly independent vectors.

\n

So the answer is B. Indeed B has 4 linearly independent vectors.

\n\n
\n

Problem 8 (10 points)

\n

\n

Problem 8 Solution

\n
\n

This problem is very similar to Problem 8 of Fall 2022 Midterm I. The solution follows the same steps.

\n
    \n
  1. Referring to Theorem 10 of Section 1.9 The Matrix of a Linear Transformation, remember the property \\[T(c\\pmb u+d\\pmb v)=cT(\\pmb u)+dT(\\pmb v)\\] We can use this property to find \\(A\\).

    \n

    First, denote \\(\\pmb u=\\begin{bmatrix}1\\\\1\\end{bmatrix}\\) and \\(\\pmb v=\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\). It is trivial to see that \\[\\begin{align}\n \\pmb{u}&=1\\cdot\\begin{bmatrix}1\\\\0\\end{bmatrix}+1\\cdot\\begin{bmatrix}0\\\\1\\end{bmatrix}=\\pmb{e}_1+\\pmb{e}_2\\\\\n \\pmb{v}&=-1\\cdot\\begin{bmatrix}1\\\\0\\end{bmatrix}+1\\cdot\\begin{bmatrix}0\\\\1\\end{bmatrix}=-\\pmb{e}_1+\\pmb{e}_2\\\\\n \\end{align}\\] This leads to \\[\\begin{align}\n \\pmb{e}_1&=\\begin{bmatrix}1\\\\0\\end{bmatrix}\n =\\frac{1}{2}\\pmb{u}-\\frac{1}{2}\\pmb{v}\\\\\n \\pmb{e}_2&=\\begin{bmatrix}0\\\\1\\end{bmatrix}\n =\\frac{1}{2}\\pmb{u}+\\frac{1}{2}\\pmb{v}\n \\end{align}\\] Then apply the property and compute \\[\\begin{align}\n T(\\pmb{e}_1)&=\\frac{1}{2}T(\\pmb{u})-\\frac{1}{2}T(\\pmb{v})\n =\\frac{1}{2}T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)-\\frac{1}{2}T\\left(\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\right)=\\begin{bmatrix}2\\\\3\\\\\\end{bmatrix}\\\\\n T(\\pmb{e}_2)&=\\frac{1}{2}T(\\pmb{u})+\\frac{1}{2}T(\\pmb{v})\n =\\frac{1}{2}T\\left(\\begin{bmatrix}1\\\\1\\end{bmatrix}\\right)+\\frac{1}{2}T\\left(\\begin{bmatrix}-1\\\\1\\end{bmatrix}\\right)=\\begin{bmatrix}1\\\\1\\end{bmatrix}\n \\end{align}\\]

  2. \n
  3. We know that the standard matrix is \\[A=[T(\\pmb{e}_1)\\quad\\dots\\quad T(\\pmb{e}_n)]\\] as we have \\(T(\\pmb{e}_1)\\) and \\(T(\\pmb{e}_2)\\) now, the standard matrix \\(A\\) is \\(\\begin{bmatrix}2 &1\\\\3 &1\\end{bmatrix}\\). It is a \\(2\\times 2\\) matrix. The inverse formula is (see Theorem 4 in Section 2.2 The Inverse of A Matrix) \\[\\begin{align}\n A&=\\begin{bmatrix}a &b\\\\c &d\\end{bmatrix}\\\\\n A^{-1}&=\\frac{1}{ad-bc}\\begin{bmatrix}d &-b\\\\-c &a\\end{bmatrix}\\\\\n \\end{align}\\] This yields \\(A^{-1}=\\begin{bmatrix}-1 &1\\\\3 &-2\\end{bmatrix}\\).

  4. \n
  5. This is the case of \\(A\\pmb x=\\pmb b\\) and we need to solve it. The augmented matrix here is \\(\\begin{bmatrix}2 &1 &7\\\\3 &1 &9\\end{bmatrix}\\). After row reduction, it becomes \\(\\begin{bmatrix}0 &1 &3\\\\1 &0 &2\\end{bmatrix}\\). This has unique solution \\(\\pmb x=\\begin{bmatrix}2\\\\3\\\\\\end{bmatrix}\\).

  6. \n
\n

📝Notes:The students should remeber the inverse formula of \\(2\\times 2\\) matrix!

\n\n
\n

Problem 9 (10 points)

\n

\n

Problem 9 Solution

\n
\n

This problem is also very similar to Problem 9 of Fall 2022 Midterm I. The solution follows the same steps.

\n
    \n
  1. The augmented matrix and the row reduction results can be seen below \\[\n\\begin{bmatrix}1 &0 &-1 &1\\\\1 &1 &h-1 &3\\\\0 &2 &h^2-3 &h+1\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &1\\\\0 &1 &h &2\\\\0 &2 &h^2-3 &h+1\\\\\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &-1 &1\\\\0 &1 &h &2\\\\0 &0 &a^2-2h-3 &h-3\\\\\\end{bmatrix}\n\\] The pivots are \\(1\\), \\(1\\), and \\(a^2-2h-3\\).

  2. \n
  3. When \\(h=3\\), the last row entries become all zeros. This system has an infinite number of solutions.

  4. \n
  5. If \\(h=-1\\), last row becomes \\([0\\,0\\,0\\,-4]\\). Now the system is inconsistent and has no solution.

  6. \n
  7. If \\(h\\) is not 3 or -1, last row becomes \\([0\\,0\\,h+1\\,1]\\). We get \\(z=\\frac{1}{h+1}\\). The system has a unique solution.

  8. \n
\n\n
\n

Problem 10 (10 points)

\n

\n

Problem 10 Solution

\n
\n

This problem is also very similar to Problem 10 of Fall 2022 Midterm I. The solution follows the same steps.

\n
    \n
  1. The row reduction is completed next. The symbol ~ before a matrix indicates that the matrix is row equivalent to the preceding matrix.
  2. \n
\n

\\[\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\1 &0 &5 &13 &20\\\\2 &0 &4 &12 &22\\\\3 &0 &2 &0 &21\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &3 &9 &9\\\\1 &0 &2 &6 &11\\\\0 &0 &-4 &-12 &-12\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &1 &3 &3\\\\0 &0 &0 &2 &0\\\\0 &0 &1 &3 &3\\end{bmatrix}\n\\] \\[\n\\sim\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &1 &3 &3\\\\0 &0 &0 &1 &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &0 &2 &4 &11\\\\0 &0 &1 &0 &3\\\\0 &0 &0 &1 &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}{1} &0 &0 &0 &5\\\\0 &0 &\\color{fuchsia}{1} &0 &3\\\\0 &0 &0 &\\color{fuchsia}{1} &0\\\\0 &0 &0 &0 &0\\end{bmatrix}\n\\]

\n
    \n
  1. Referring to Theorem 12 Section 2.8 Matrix Algebra and the Warning message below that (quoted below)

    \n
    \n

    Warning: Be careful to use pivot columns of \\(A\\) itself for the basis of Col \\(A\\). Thecolumns of an echelon form \\(B\\) are often not in the column space of \\(A\\).

    \n
    \n

    So the pivot columns of the original matrix \\(A\\) form a basis for the column space of \\(A\\). The basis is the set of columns 1, 3, and 4. \\[\n \\begin{Bmatrix}\\begin{bmatrix}1\\\\1\\\\2\\\\3\\end{bmatrix},\n \\begin{bmatrix}2\\\\5\\\\4\\\\2\\end{bmatrix},\n \\begin{bmatrix}4\\\\13\\\\12\\\\0\\end{bmatrix}\\end{Bmatrix}\n \\]

  2. \n
  3. Referring to Section 2.8 Subspaces of \\(\\mathbb R^n\\), by definition the null space of a matrix \\(A\\) is the set Nul \\(A\\) of all solutions of the homogeneous equation \\(A\\pmb{x}=\\pmb{0}\\). Also \"A basis for a subspace \\(H\\) of \\(\\mathbb R^n\\) is a linearly independent set in \\(H\\) that spans \\(H\\)\".

    \n

    Now write the solution of \\(A\\mathrm x=\\pmb 0\\) in parametric vector form \\[[A\\;\\pmb 0]\\sim\\begin{bmatrix}\\color{fuchsia}{1} &0 &0 &0 &5 &0\\\\0 &0 &\\color{fuchsia}{1} &0 &3 &0\\\\0 &0 &0 &\\color{fuchsia}{1} &0 &0\\\\0 &0 &0 &0 &0 &0\\end{bmatrix}\\]

    \n

    The general solution is \\(x_1=-5x_5\\), \\(x_3=-3x_5\\), \\(x_4=0\\), with \\(x_2\\) and \\(x_5\\) free. This can be written as \\[\n \\begin{bmatrix}x_1\\\\x_2\\\\x_3\\\\x_4\\\\x_5\\end{bmatrix}=\n \\begin{bmatrix}-5x_5\\\\x_2\\\\-3x_5\\\\0\\\\x_5\\end{bmatrix}=\n x_4\\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix}+\n x_5\\begin{bmatrix}-5\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\n \\] So the basis for Nul \\(A\\) is \\[\n \\begin{Bmatrix}\\begin{bmatrix}0\\\\1\\\\0\\\\0\\\\0\\end{bmatrix},\n \\begin{bmatrix}-5\\\\0\\\\-3\\\\0\\\\1\\end{bmatrix}\\end{Bmatrix}\n \\]

  4. \n
\n\n
\n

Summary

\n

Here is the table listing the key knowledge points for each problem in this exam:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Problem #Points of Knowledge
1Determinant and its Properties
2Rank and Dimension of the Null Space of a Matrix, Pivot Columns, Row Reduction Operation
3Linear Transformation, Onto \\(\\mathbb R^m\\), Linear System Consistency
4Homogeneous Linear Systems, One-to-One Mapping Linear Transformation, the Column Space of the Matrix
5Linear Dependency, Invertible Matrix, Determinant, Rank and Dimension of the Null Space of Matrix
6The Adjugate of Matrix, The (\\(i,j\\))-cofactor of Matrix
7Linear Independency, Vector Set Spanning Space \\(\\mathbb R^n\\)
8Linear Transformation Properties, Standard Matrix for a Linear Transformation
9Row Echelon Form, Linear System Solution Set and Consistency
10Reduced Row Echelon Form, Basis for the Column Vector Space and the Null Space
\n

As can be seen, it has a good coverage of the topics of the specified sections from the textbook. Students should carefully review those to prepare for this and similar exams.

\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Purdue MA 26500 Spring 2023 Midterm II Solutions","url":"/en/2024/02/29/Purdue-MA265-2023-Spring-Midterm2/","content":"

Here comes the solution and analysis for Purdue MA 26500 Spring 2023 Midterm II. This second midterm covers topics in Chapter 4 (Vector Spaces) and Chapter 5 (Eigenvalues and Eigenvectors) of the textbook.

\n

Introduction

\n

Purdue Department of Mathematics provides a linear algebra course MA 26500 every semester, which is mandatory for undergraduate students of almost all science and engineering majors.

\n

Textbook and Study Guide

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

MA 26500 textbook is Linear Algebra and its Applications (6th Edition) by David C. Lay, Steven R. Lay, and Judi J. McDonald. The authors have also published a student study guide for it, which is available for purchase on Amazon as well.

\n
\n

Exam Information

\n

MA 26500 midterm II covers the topics of Sections 4.1 – 5.7 in the textbook. It is usually scheduled at the beginning of the thirteenth week. The exam format is a combination of multiple-choice questions and short-answer questions. Students are given one hour to finish answering the exam questions.

\n

Based on the knowledge of linear equations and matrix algebra learned in the book chapters 1 and 2, Chapter 4 leads the student to a deep dive into the vector space framework. Chapter 5 introduces the important concepts of eigenvectors and eigenvalues. They are useful throughout pure and applied mathematics. Eigenvalues are also used to study differential equations and continuous dynamical systems, they provide critical information in engineering design,

\n

Reference Links

\n\n

Spring 2023 Midterm II Solutions

\n

Problem 1 (10 points)

\n

\n

Problem 1 Solution

\n
\n

A For \\(5\\times 7\\) matrix, if \\(rank(A)=5\\), the dimension of the null space is \\(7-5=2\\). So this is wrong.

\n

B The matrix has 7 columns, but there are only 5 pivot columns, so the columns of \\(A\\) are NOT linearly independent. It is wrong.

\n

C \\(A^T\\) is a \\(7\\times 5\\) matrix, and the rank of \\(A^T\\) is no more than 5. This statement is wrong.

\n

D Because there are 5 pivots, each row has one pivot. Thus the rows of \\(A\\) are linearly independent. This statement is TRUE.

\n

E From statement D, it can be deduced that the dimension of the row space is 5, not 2.

\n

The answer is D.

\n\n
\n

Problem 2 (10 points)

\n

\n

Problem 2 Solution

\n
\n

The vector in this subspace \\(H\\) can be represented as \\[\na\\begin{bmatrix}1\\\\1\\\\0\\\\0\\end{bmatrix}+\nb\\begin{bmatrix}-2\\\\-1\\\\1\\\\0\\end{bmatrix}+\nc\\begin{bmatrix}9\\\\6\\\\-3\\\\0\\end{bmatrix}+\nd\\begin{bmatrix}5\\\\5\\\\1\\\\5\\end{bmatrix}+\ne\\begin{bmatrix}4\\\\-3\\\\-9\\\\-10\\end{bmatrix}\n\\]

\n

Here the transformation matrix \\(A\\) has 5 columns and each has 4 entries. Hence these column vectors are not linearly independent.

\n
\n

Note that row operations do not affect the dependence relations between the column vectors. This makes it possible to use row reduction to find a basis for the column space.

\n
\n

\\[\n\\begin{align}\n&\\begin{bmatrix}1 &-2 &9 &5 &4\\\\1 &-1 &6 &5 &-3\\\\0 &1 &-3 &1 &-9\\\\0 &0 &0 &5 &-10\\end{bmatrix}\\sim\n\\begin{bmatrix}1 &-2 &9 &5 &4\\\\0 &1 &-3 &0 &-7\\\\0 &1 &-3 &1 &-9\\\\0 &0 &0 &5 &-10\\end{bmatrix}\\\\\n\\sim&\\begin{bmatrix}1 &-2 &9 &5 &4\\\\0 &1 &-3 &0 &-7\\\\0 &0 &0 &1 &-2\\\\0 &0 &0 &5 &-10\\end{bmatrix}\\sim\n\\begin{bmatrix}\\color{fuchsia}1 &-2 &9 &5 &4\\\\0 &\\color{fuchsia}1 &-3 &0 &-7\\\\0 &0 &0 &\\color{fuchsia}1 &-2\\\\0 &0 &0 &0 &0\\end{bmatrix}\n\\end{align}\n\\]

\n

The dimension of \\(H\\) is the number of linearly independent columns of the matrix, which is the number of pivots in \\(A\\)'s row echelon form. So the dimension is 3.

\n

The answer is C.

\n\n
\n

Problem 3 (10 points)

\n

\n

Problem 3 Solution

\n
\n

First, find the eigenvalues for the matrix \\[\n\\begin{align}\n\\det A-\\lambda I &=\\begin{vmatrix}2-\\lambda &2\\\\3 &1-\\lambda\\end{vmatrix}=(\\lambda^2-3\\lambda+2)-6\\\\&=\\lambda^2-3\\lambda-4=(\\lambda+1)(\\lambda-4)=0\n\\end{align}\n\\] The above gives two real eigenvalues \\(-1\\) and \\(4\\). Since they have opposite signs, the origin is a saddle point.

\n

The answer is A.

\n\n
\n

Problem 4 (10 points)

\n

\n

Problem 4 Solution

\n
\n

(i) is NOT true. Referring to Theorem 4 of Section 5.2 \"The Characteristic Equation\",

\n
\n

If \\(n\\times n\\) matrices \\(A\\) and \\(B\\) are similar, then they have the same characteristic polynomial and hence the same eigenvalues (with the same multiplicities).

\n
\n

But the reverse statement is NOT true. They are matrices that are not similar even though they have the same eigenvalues.

\n

(ii) is NOT true either. Referring to Theorem 6 of Section 5.3 \"Diagonalization\",

\n
\n

An \\(n\\times n\\) matrix with \\(n\\) distinct eigenvalues is diagonalizable.

\n
\n

The book mentions that the above theorem provides a sufficient condition for a matrix to be diagonalizable. So the reverse statement is NOT true. There are examples that a diagonalizable matrix has eigenvalues with multiplicity 2 or more.

\n

(iii) Since the identity matrix is symmetric, and \\(\\det A=\\det A^T\\) for \\(n\\times n\\) matrix, we can write \\(\\det (A-\\lambda I) = \\det (A-\\lambda I)^T = \\det(A^T-\\lambda I)\\). So matrix \\(A\\) and its transpose have the same eigenvalues. This statement is TRUE.

\n

(iv) This is definitely TRUE as we can find eigenvectors that are linearly independent and span \\(\\mathbb R^n\\).

\n

(v) If matrix \\(A\\) has zero eigenvalue, \\(\\det A-0I=\\det A=0\\), it is not invertible. This statement is TRUE.

\n

In summary, statements (iii), (iv), and (v) are TRUE. The answer is E.

\n\n
\n

Problem 5 (10 points)

\n

\n

Problem 5 Solution

\n
\n

A This vector set does not include zero vector (\\(x = y = 0\\)). So it is not a subspace of \\(V\\).

\n

B For eigenvalue 3, we can find out the eigenvector from \\(\\begin{bmatrix}0 &0\\\\2 &0\\end{bmatrix}\\pmb v=\\pmb 0\\), it is \\(\\begin{bmatrix}0\\\\\\ast\\end{bmatrix}\\). All vectors in this set satisfy three subspace properties. So this one is good.

\n

C This cannot be the right choice. Since the 3rd entry is always 1, the vector set cannot be closed under vector addition and multiplication by scalars. Also, it does not include zero vector either.

\n

D For \\(p(x)=a_0+a_1x+a_2x^2\\) and \\(p(1)p(2)=0\\), this gives \\[(a_0+a_1+a_2)(a_0+2a_1+4a_2)=0\\] To verify if this is closed under vector addition. Define \\(q(x)=b_0+b_1x+b_2x^2\\) that has \\(q(1)q(2)=0\\), this gives \\[(b_0+b_1+b_2)(b_0+2b_1+4b_2)=0\\] Now let \\(r(x)=p(x)+q(x)=c_0+c_1x+c_2x^2\\), where \\(c_i=a_i+b_i\\) for \\(i=0,1,2\\). Is it true that \\[(c_0+c_1+c_2)(c_0+2c_1+4c_2)=0\\] No, it is not necessarily the case. This one is not the right choice either.

\n

E Invertible matrix indicates that its determinant is not 0. The all-zero matrix is certainly not invertible, so it is not in the specified set. Moreover, two invertible matrices can add to a non-invertible matrix, such as the following example \\[\n\\begin{bmatrix}2 &1\\\\1 &2\\end{bmatrix}+\\begin{bmatrix}-2 &1\\\\-1 &-2\\end{bmatrix}=\\begin{bmatrix}0 &2\\\\0 &0\\end{bmatrix}\n\\] This set is NOT a subspace of \\(V\\).

\n

The answer is B.

\n\n
\n

Problem 6 (10 points)

\n

\n

Problem 6 Solution

\n
\n

Recall from the Problem 4 solution that a matrix with \\(n\\) distinct eigenvalues is diagonalizable.

\n

(i) The following calculation shows this matrix has two eigenvalues 4 and 1. So it is diagonalizable. \\[\\begin{vmatrix}2-\\lambda &2\\\\1 &3-\\lambda\\end{vmatrix}=(\\lambda^2-5\\lambda+6)-2=(\\lambda-1)(\\lambda-4)=0\\]

\n

(ii) It is easy to see that there is one eigenvalue \\(-3\\) with multiplicity 2. However, we can only get one eigenvector \\(\\begin{bmatrix}1\\\\0\\end{bmatrix}\\) for such eigenvalue. So it is NOT diagonalizable.

\n

(iii) To find out the eigenvalues for this \\(3\\times 3\\) matrix, do the calculation as below \\[\n\\begin{vmatrix}2-\\lambda &3 &5\\\\0 &2-\\lambda &1\\\\0 &1 &2-\\lambda\\end{vmatrix}=(2-\\lambda)\\begin{vmatrix}2-\\lambda &1\\\\1 &2-\\lambda\\end{vmatrix}=(2-\\lambda)(\\lambda-3)(\\lambda-1)\n\\] So we get 3 eigenvalues 2, 3, and 1. This matrix is diagonalizable.

\n

(iv) This is an upper triangular matrix, so the diagonal entries (5, 4, 2) are all eigenvalues. As this matrix has three distinct eigenvalues, it is diagonalizable.

\n

Since only (ii) is not diagonalizable, the answer is E.

\n\n
\n

Problem 7 (10 points)

\n

\n

Problem 7 Solution

\n
\n

This problem involves complex eigenvalues.

\n

Step 1: Find the eigenvalue of the given matrix \\[\n\\begin{vmatrix}1-\\lambda &-1\\\\1 &1-\\lambda\\end{vmatrix}=\\lambda^2-2\\lambda+2=0\n\\] Solve this with the quadratic formula \\[\n\\lambda=\\frac {-b\\pm {\\sqrt {b^{2}-4ac}}}{2a}=\\frac {-(-2)\\pm {\\sqrt {(-2)^2-4\\times 1\\times 2}}}{2\\times 1}=1\\pm i\n\\]

\n

Step 2: Find the corresponding eigenvector for \\(\\lambda=1+i\\) \\[\n\\begin{bmatrix}-i &-1\\\\1 &-i\\end{bmatrix}\\sim\\begin{bmatrix}0 &0\\\\1 &-i\\end{bmatrix}\n\\] This gives \\(x_1=ix_2\\), so the eigervector can be \\(\\begin{bmatrix}i\\\\1\\end{bmatrix}\\).

\n

Step 3: Generate the real solution

\n

From Section 5.7 \"Applications to Differential Equations\", we learn that the general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] For a real matrix, complex eigenvalues and associated eigenvectors come in conjugate pairs. The real and imaginary parts of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) are (real) solutions of \\(\\pmb x'(t)=A\\pmb x(t)\\), because they are linear combinations of \\(\\pmb{v}_1 e^{\\lambda_1 t}\\) and \\(\\pmb{v}_2 e^{\\lambda_2 t}\\). (See the proof in \"Complex Eigenvalues\" of Section 5.7)

\n

Now use Euler's formula (\\(e^{ix}=\\cos x+i\\sin x\\)), we have \\[\\pmb{v}_1 e^{\\lambda_1 t}=e^t(\\cos t+i\\sin t)\\begin{bmatrix}i\\\\1\\end{bmatrix}\\\\\n=e^t\\begin{bmatrix}-\\sin t+i\\cos t\\\\\\cos t+i\\sin t\\end{bmatrix}\\] The general REAL solution is the linear combination of the REAL and IMAGINARY parts of the result above, it is \\[c_1 e^t\\begin{bmatrix}-\\sin t\\\\\\cos t\\end{bmatrix}+\nc_2 e^t\\begin{bmatrix}\\cos t\\\\\\sin t\\end{bmatrix}\\]

\n

At first glance, none on the list matches our answer above. However, let's inspect this carefully. We can exclude C and D first since they both have \\(e^{-t}\\) that is not in our answer. Next, it is impossible to be E because it has no minus sign.

\n

Now between A and B, which one is most likely to be the right one? We see that B has \\(-\\cos t\\) on top of \\(\\sin t\\). That could not match our answer no matter what \\(c_2\\) is. If we switch \\(c_1\\) and \\(c_2\\) of A and inverse the sign of the 2nd vector, A would become the same as our answer. Since \\(c_1\\) and \\(c_2\\) are just scalars, this deduction is reasonable.

\n

So the answer is A.

\n\n
\n

Problem 8 (10 points)

\n

\n

Problem 8 Solution

\n
\n

(1) Directly apply \\(p(t)=t^2-1\\) to the mapping function \\[T(t^2-1)=0^2-1+(1^2-1)t+(2^2-1)t^2=-1+3t^2\\]

\n

(2) Denote \\(p(t)=a_0+a_1t+a_2t^2\\), \\(T(p(t))=b_0+b_1t+b_2t^2\\), then \\[\nT(a_0+a_1t+a_2t^2)=a_0+(a_0+a_1+a_2)t+(a_0+2a_1+4a_2)t^2\n\\] So \\[\n\\begin{align}\na_0 &&=b_0\\\\\na_0 &+ a_1 + a_2 &=b_1\\\\\na_0 &+ 2a_1 + 4a_2 &=b_2\n\\end{align}\n\\] This gives the \\([T]_B=\\begin{bmatrix}1 &0 &0\\\\1 &1 &1\\\\1 &2 &4\\end{bmatrix}\\).

\n

Alternatively, we can form the same matrix with the transformation of each base vector: \\[\\begin{align}\nT(1)&=1+t+t^2 => \\begin{bmatrix}1\\\\1\\\\1\\end{bmatrix}\\\\\nT(t)&=0+t+2t^2 => \\begin{bmatrix}0\\\\1\\\\2\\end{bmatrix}\\\\\nT(t^2)&=0+t+4t^2 => \\begin{bmatrix}0\\\\1\\\\4\\end{bmatrix}\\\n\\end{align}\\]

\n\n
\n

Problem 9 (10 points)

\n

\n

Problem 9 Solution

\n
\n

(1) Find the eigenvalues with \\(\\det (A-\\lambda I)=0\\) \\[\n\\begin{vmatrix}1-\\lambda &2 &-1\\\\0 &3-\\lambda &-1\\\\0 &-2 &2-\\lambda\\end{vmatrix}=(1-\\lambda)\\begin{vmatrix}3-\\lambda &-1\\\\-2 &2-\\lambda\\end{vmatrix}=(1-\\lambda)(\\lambda-4)(\\lambda-1)\n\\] So there are \\(\\lambda_1=\\lambda_2=1\\), and \\(\\lambda_3=4\\).

\n

Next is to find the eigenvectors for each eigenvalue

\n
    \n
  • For \\(\\lambda_1=\\lambda_2=1\\), apply row reduction to the agumented matrix of the system \\((A-\\lambda I)\\pmb x=\\pmb 0\\) \\[\n\\begin{bmatrix}0 &2 &-1 &0\\\\0 &2 &-1 &0\\\\0 &-2 &1 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}0 &2 &-1 &0\\\\0 &0 &0 &0\\\\0 &0 &0 &0\\end{bmatrix}\n\\] With two free variables \\(x_1\\) and \\(x_2\\), we get \\(x_3=2x_2\\). So the parametric vector form can be written as \\[\n\\begin{bmatrix}x_1\\\\x_2\\\\x_3\\end{bmatrix}=\nx_1\\begin{bmatrix}1\\\\0\\\\0\\end{bmatrix}+x_2\\begin{bmatrix}0\\\\1\\\\2\\end{bmatrix}\n\\] So the eigenvectors are \\(\\begin{bmatrix}1\\\\0\\\\0\\end{bmatrix}\\) and \\(\\begin{bmatrix}0\\\\1\\\\2\\end{bmatrix}\\).

  • \n
  • For \\(\\lambda_3=4\\), follow the same process \\[\n\\begin{bmatrix}-3 &2 &-1 &0\\\\0 &-1 &-1 &0\\\\0 &-2 &-2 &0\\end{bmatrix}\\sim\n\\begin{bmatrix}3 &-2 &1 &0\\\\0 &1 &1 &0\\\\0 &0 &0 &0\\end{bmatrix}\n\\] With one free variable \\(x_3\\), we get \\(x_1=x_2=-x_3\\). So the eigenvector can be written as \\(\\begin{bmatrix}1\\\\1\\\\-1\\end{bmatrix}\\) (or \\(\\begin{bmatrix}-1\\\\-1\\\\1\\end{bmatrix}\\)).

  • \n
\n

(2) We can directly construct \\(P\\) from the vectors in last step, and construct \\(D\\) from the corresponding eigenvalues. Here are the answers: \\[\nP=\\begin{bmatrix}\\color{fuchsia}1 &\\color{fuchsia}0 &\\color{blue}1\\\\\\color{fuchsia}0 &\\color{fuchsia}1 &\\color{blue}1\\\\\\color{fuchsia}0 &\\color{fuchsia}2 &\\color{blue}{-1}\\end{bmatrix},\\;\nD=\\begin{bmatrix}\\color{fuchsia}1 &0 &0\\\\0 &\\color{fuchsia}1 &0\\\\0 &0 &\\color{blue}4\\end{bmatrix}\n\\]

\n\n
\n

Problem 10 (10 points)

\n

\n

Problem 10 Solution

\n
\n

(1) Find the eigenvalues with \\(\\det (A-\\lambda I)=0\\)
\n\\[\\begin{vmatrix}-4-\\lambda &-5\\\\2 &3-\\lambda\\end{vmatrix}=(\\lambda^2+\\lambda-12)+10=(\\lambda+2)(\\lambda-1)=0\\] So there are two eigervalues \\(-2\\) and 1. Next is to find the eigenvectors for each eigenvalue.

\n

For \\(\\lambda=-2\\), the matrix becomes \\[\\begin{bmatrix}-2 &-5\\\\2 &5\\end{bmatrix}=\\begin{bmatrix}0 &0\\\\2 &5\\end{bmatrix}\\] This yields eigen vector \\(\\begin{bmatrix}5\\\\-2\\end{bmatrix}\\).

\n

For \\(\\lambda=1\\), the matrix becomes \\[\\begin{bmatrix}-5 &-5\\\\2 &2\\end{bmatrix}=\\begin{bmatrix}0 &0\\\\1 &1\\end{bmatrix}\\] This yields eigen vector \\(\\begin{bmatrix}1\\\\-1\\end{bmatrix}\\).

\n

(2) The general solution to a matrix differential equation is \\[\\pmb x(t)=c_1\\pmb{v}_1 e^{\\lambda_1 t}+c_2\\pmb{v}_2 e^{\\lambda_2 t}\\] So from this, since we already found out the eigenvalues and the corresponding eigenvectors, we can write down \\[\n\\begin{bmatrix}x(t)\\\\y(t)\\end{bmatrix}=c_1\\begin{bmatrix}5\\\\-2\\end{bmatrix}e^{-2t}+c_2\\begin{bmatrix}1\\\\-1\\end{bmatrix}e^t\n\\]

\n

(3) Apply the initial values of \\(x(0)\\) and \\(y(0)\\), here comes the following equations: \\[\\begin{align}\n5c_1+c_2&=-3\\\\\n-2c_1-c_2&=0\n\\end{align}\\] This gives \\(c_1=-1\\) and \\(c_2=2\\). So \\(x(1)+y(1)=-5e^{-2}+2e^1+2e^{-2}-2e^{-1}=-3e^{-2}\\).

\n\n
\n

Summary

\n

Here are the key knowledge points covered by this exam:

\n
    \n
  • Linear dependency, Rank, and dimension of null space
  • \n
  • Vector Space, Subspace Properties, and Basis
  • \n
  • Eigenvalues, eigenvectors, and the origin graph
  • \n
  • Similar matrices and diagonalization
  • \n
  • Applications to Differential Equations
  • \n
\n","categories":["Study Notes"],"tags":["Linear Algebra"]},{"title":"Build an Awesome Raspberry Pi NAS for Home Media Streaming","url":"/en/2021/12/29/RPi-NAS-Plex/","content":"

Network Attached Storage (NAS) provides data access to a heterogeneous group of clients over computer networks. As hard drive prices continue to drop, NAS devices have made their way into the homes of the masses. Leading brands in the SMB and home NAS market, such as Synology, have their products range in price from as low as ﹩300 to ﹩700 for the high models. But if you are a Raspberry Pi player, you can build a very nice home NAS and streaming service for only about half the cost of the lowest price.

\n

Knowledge obtained on the papers always feels shallow, must know this thing to practice.
LU You (Chinese historian and poet of the Southern Song Dynasty)

\n
\n

This blog records the whole process of building a Raspberry Pi NAS and home media server, including project planning, system implementation, and performance review. It also covers some important experiences and lessons that could hopefully benefit anyone interested in this DIY project.

\n

Project Planning

\n

Raspberry Pi 4B features an upgraded 1.8GHz Broadcom BCM2711(quad-core Cortex-A72)processor and onboard RAM up to 8GB. It includes two new USB 3.0 ports and a full-speed Gigabit Ethernet interface. The power supply is also updated to a USB-C connector. All these greatly improve system throughput and overall comprehensive performance, and we can use them to create a full-featured home NAS.

\n

For NAS system software, OpenMediaVault (OMV) is a complete NAS solution based on Debian Linux. It is a Linux rewrite of the well-known free and open-source NAS server system FreeNAS (based on FreeBSD). The salient features of OMV are

\n
    \n
  • Simple and easy-to-use out-of-the-box solution, no need for expert-level knowledge of computer networking and storage systems
  • \n
  • Available for x86-64 and ARM platforms with a full Web Administration interface
  • \n
  • Supports a variety of different protocols (SFTP、SMB/CIFS, NFS, etc.) for file storage access
  • \n
  • Can be controlled via SSH (if enabled), and provides Access Right Management for users and groups
  • \n
\n

While primarily designed for home environments or small home offices, OMV's use is not limited to those scenarios. The system is built on a modular design. It can be easily extended with available plugins right after the installation of the base system. OMV is the NAS server system software we are looking for.

\n

The NAS system with media playback services provides an excellent audio/video-on-demand experience in a home network environment. Plex Media Server software integrates Internet media services (YouTube, Vimeo, TED, etc.) and local multimedia libraries to provide streaming media playback on users' various devices. The features of Plex for managing local libraries are

\n
    \n
  • Centralized management and easy sharing of a single library
  • \n
  • Web interface with media resource navigation, streaming playback
  • \n
  • Real-time saving and resuming of playback progress
  • \n
  • Multi-user support and hierarchical playback rights settings
  • \n
\n

The Plex Media Server software itself is free and supports a wide range of operating systems, making it ideal for integration with home NAS.

\n

These cover all the software needed for our NAS project, but they are not enough for a complete NAS system. We also need a preferred case, otherwise, the Raspberry Pi NAS will only run bare metal. Although there are many cases available in the market for Raspberry Pi 4B, as a NAS system we need a case kit that can accommodate at least 1-2 internal SSD/HDD and must also have a good heat dissipation design.

\n

After some review and comparison, we chose Geekworm's NASPi Raspberry Pi 4B NAS storage kit. NASPi is a NUC (Next Unit of Computing) style NAS storage kit designed for the latest Raspberry Pi 4B. It consists of three components:

\n
    \n
  1. X823 shield board, which provides storage function for 2.5-inch SDD/HDD
  2. \n
  3. X-C1 adapter board, which adjusts all Raspberry Pi 4B interfaces to the back of the case and provides power management and safe shutdown function
  4. \n
  5. Temperature-controlled PWM (Pulse-Width Modulation) fan as the cooling system
  6. \n
\n

All these components are packed into a case made of aluminum alloy with an anodized surface.

\n

Thereon our NAS project can be planned with the following subsystems:

\n
    \n
  • Hardware System:\n
      \n
    • Raspberry Pi 4B 8GB RAM
    • \n
    • 32GB microSD for OS storage
    • \n
    • NASPi NAS storage kit
    • \n
    • 15-20W USB-C power adaptor
    • \n
    • 500GB internal SSD(USB 3.0)
    • \n
    • 2TB external HDD(USB 3.0)
    • \n
  • \n
  • Software System:\n
      \n
    • Raspberry Pi OS Lite(with no desktop environment)
    • \n
    • OMV for NAS file server
    • \n
    • Plex media server providing streaming service
    • \n
  • \n
\n

It is important to note that NAS servers are generally headless systems without a keyboard, mouse, or monitor. This poses some challenges for the installation, configuration, and tuning of hardware and software systems. In practice, as described in the next section, we run an SSH terminal connection to complete the basic project implementation process.

\n

System Implementation

\n

The execution of this project was divided into four stages, which are described in detail as follows.

\n

Prepare Raspberry Pi 4B

\n

In the first stage, we need to prepare the Raspberry Pi OS and do some basic unit tests. This is important, if we delay the OS test until the entire NSAPi kit is assembled, it will be troublesome to find problems with the Raspberry Pi then.

\n

Bake Raspberry Pi OS

\n

First, insert the microSD card into the USB adapter and connect it to the macOS computer, then go to the Raspberry Pi website and download the Raspberry Pi Imager software to run. From the application screen, click CHOOSE OS > Raspberry Pi OS (other) > Raspberry Pi OS Lite (32-bit) step by step. This selects the lightweight Raspberry Pi OS that does not require a desktop environment, and then click CHOOSE STORAGE to pick the microSD card.

\n

Next is a trick - hit the ctrl-shift-x key combination and the following advanced options dialog box will pop up Here is exactly the option we need to enable SSH on boot up - Enable SSH. It also allows the user to pre-set a password for the default username pi (default is raspberry). Once set up, click SAVE to return to the main page and then click WRITE to start formatting the microSD card and writing OS to it. When finished, remove the microSD card and insert it into the Raspberry Pi, connect the Ethernet cable then power it up.

\n

Probe IP Address

\n

At this point we encountered a problem: since the installed system does not have a desktop environment, it cannot connect to the keyboard, mouse, and monitor, so how do we find its IP address? There are two ways:

\n
    \n
  1. connect to the home router's management WebUI and find the address for the hostname 'raspberry'.
  2. \n
  3. run the Nmap tool to scan the target subnet and check the changes before and after the Raspberry Pi boots up
  4. \n
\n

The log of the Nmap tool run can be seen below. Notice that a new IP address 192.168.2.4 is showing up in the scan report. Rerunning Nmap against this address alone, we saw that TCP port 22 was open. We could roughly determine that this might be our newly online Raspberry Pi:

\n
❯ nmap -sn 192.168.2.0/24
Starting Nmap 7.92 ( https://nmap.org ) at 2021-11-28 21:07 PST
Nmap scan report for router.sx.com (192.168.2.1)
Host is up (0.0050s latency).
Nmap scan report for 192.168.2.3
Host is up (0.0048s latency).
Nmap scan report for 192.168.2.4 ## New IP after Raspberry Pi boots up
Host is up (0.0057s latency).
Nmap done: 256 IP addresses (3 hosts up) scanned in 15.31 seconds

❯ nmap 192.168.2.4
Nmap scan report for 192.168.2.4
Host is up (0.0066s latency).
Not shown: 999 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
\n

System Update and Upgrade

\n

Next, try SSH connection

\n
❯ ssh pi@192.168.2.4
pi@192.168.2.4's password:
Linux raspberrypi 5.10.63-v7l+ #1488 SMP Thu Nov 18 16:15:28 GMT 2021 armv7l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Dec 24 19:46:15 2021 from 192.168.2.3
pi@raspberrypi:~ $
\n

Once confirmed, we executed the following commands in the Raspberry Pi to update and upgrade the system:

\n
pi@raspberrypi:~ $ sudo apt update && sudo apt upgrade
\n

Network Connectivity Test

\n

This stage concluded with the stability test of the Raspberry Pi 4B system Ethernet connection. The test was executed on a macOS computer using the simple ping command, setting the -i 0.05 option to specify 20 packets per second and the -t 3600 option for one hour run

\n
❯ sudo ping -i 0.05 192.168.2.4 -t 3600
\n

There should be no more than 1% packet loss or timeout on a subnet with no wireless connectivity, otherwise, it should be checked for troubleshooting. As a matter of fact, in our test, it was happening that nearly 10% of ping packets got lost and the SSH connection dropped intermittently. Searching the Internet, we found that there have been quite a few reports of similar issues with the Raspberry Pi 4B Ethernet connection. The analysis and suggestions given by people on the relevant forums focus on the following

\n
    \n
  1. Unstable power supply accounts for packet loss, and needs to be replaced with a reliable USB-C power adapter of 15W or more.
  2. \n
  3. Energy-efficient Ethernet (Energy-Efficient Ethernet) malfunction, can be fixed by disabling it.
  4. \n
  5. The full-speed Gigabit Ethernet connection function is faulty and has to be downgraded to 100Mbit/s for stable use.
  6. \n
\n

Practically, we tried all of the above with little success. Later, we found that the home router connected to the Raspberry Pi 4B was a Belkin N750 DB made in 2011. Although it provides Wi-Fi dual-band 802.11n and 4 Gigabit Ethernet ports, the manufacturing date is too long ago, which makes people doubt its interoperability. Also points 2 and 3 of the above report are essentially interoperability issues. Thinking of these, we immediately ordered the TP-Link TL-SG105 5-port Gigabit Ethernet switch. After receiving it, we extended the Gigabit Ethernet port of N750 with TL-SG105, connected Raspberry Pi 4B to TL-SG105, and retested it. Sure enough, this time the ping packet loss rate was less than 0.1% and the SSH connection became solid.

\n

The conclusion is that the Raspberry Pi 4B Gigabit Ethernet interface may have compatibility issues with some older devices, which can be solved by inserting a desktop switch with good interoperability between the two.

\n

NSAPi Kit Assembly

\n

In the second stage, we assembled the NSAPi storage kit, intending to finish all hardware installation and complete the standalone NAS body.

\n

Prepare Internal SSD

\n

The NSAPi supports either an internal SSD or HDD. The project picked a Samsung 870 EVO 500GB internal SSD, here we ought to first make sure the SSD works properly on its own, otherwise, we would have to disassemble the NASPi to replace it. The SSD can be hooked up to Windows for file systems and basic read/write operation checks. In the case of a newly purchased SSD, the following steps can be done on Windows to quickly format it:

\n
    \n
  1. Click on Start or the Windows button, select Control Panel > System and Security
  2. \n
  3. Select Administrative Tools > Computer Management > Disk management
  4. \n
  5. Choose the disk to be formatted, right-click then select Format
  6. \n
  7. Check the following in the Dialog box pop-up\n
      \n
    • File System → NTFS
    • \n
    • Allocation Unit Size → Default
    • \n
    • Volume Label → (enter volume name)
    • \n
    • Perform a quick format
    • \n
  8. \n
  9. Click the OK button to start a fast format for the SSD
  10. \n
\n

⚠️Note: Here the chosen file system is NTFS. OMV supports NTFS mounting and reads/writes.

\n

PWM Fan Control

\n

Before the actual hardware assembly, a special software provided by Geekworm - PWM fan control script - must be installed. PWM fan speed adjustment to temperature change is a major feature that lets NASPi stand out from other hardware solutions. So this step is critical.

\n

Referring to Geekworm's X-C1 software wiki page, the installation command sequence on the SSH session connected to the Raspberry Pi 4B system is as follows

\n
sudo apt-get install -y git pigpio 
sudo apt-get install -y python3-pigpio
sudo apt-get install -y python3-smbus
git clone https://github.com/geekworm-com/x-c1.git
cd x-c1
sudo chmod +x *.sh
sudo bash install.sh
echo "alias xoff='sudo /usr/local/bin/x-c1-softsd.sh'" >> ~/.bashrc
\n

If you can't do git clone directly on Raspberry Pi 4B, you can first download the X-C1 software on the SSH client, then transfer it to Raspberry Pi 4B using scp. After that, continue to execute the subsequent commands

\n
❯ scp -r x-c1 pi@192.168.2.4:/home/pi/
\n

How does X-C1 software control PWM fan?

\n
\n

The core of X-C1 software is a Python script named fan.py, which is presented below

\n
#!/usr/bin/python
import pigpio
import time

servo = 18

pwm = pigpio.pi()
pwm.set_mode(servo, pigpio.OUTPUT)
pwm.set_PWM_frequency( servo, 25000 )
pwm.set_PWM_range(servo, 100)
while(1):
#get CPU temp
file = open("/sys/class/thermal/thermal_zone0/temp")
temp = float(file.read()) / 1000.00
temp = float('%.2f' % temp)
file.close()

if(temp > 30):
pwm.set_PWM_dutycycle(servo, 40)

if(temp > 50):
pwm.set_PWM_dutycycle(servo, 50)

if(temp > 60):
pwm.set_PWM_dutycycle(servo, 70)

if(temp > 70):
pwm.set_PWM_dutycycle(servo, 80)

if(temp > 75):
pwm.set_PWM_dutycycle(servo, 100)

if(temp < 30):
pwm.set_PWM_dutycycle(servo, 0)
time.sleep(1)
\n

Its logic is quite simple. With the pigpio module imported, it first initializes a PWM control object and then starts a while loop with a 1-second sleep cycle inside. The CPU temperature is read at each cycle, and the duty cycle of PWM is set according to the temperature level to control the fan speed. The duty cycle is 0 when it is lower than 30℃, and the fan stops; when it is higher than 75℃, the duty cycle is 100, and the fan spins at full speed. Users can modify the temperature threshold and duty cycle parameters in the program to customize the PWM fan control.

\n\n
\n

In addition, the following pi-temp.sh script, which reads out the GPU and CPU temperatures, is also useful

\n
pi@raspberrypi:~ $ cat ./pi-temp.sh
#!/bin/bash
# Script: pi-temp.sh
# Purpose: Display the ARM CPU and GPU temperature of Raspberry Pi
# -------------------------------------------------------
cpu=$(</sys/class/thermal/thermal_zone0/temp)
echo "$(date) @ $(hostname)"
echo "-------------------------------------------"
echo "GPU => $(vcgencmd measure_temp)"
echo "CPU => temp=$((cpu/1000))’C"

pi@raspberrypi:~ $ ./pi-temp.sh
Mon 29 Nov 06:59:17 GMT 2021 @ raspberrypi
-------------------------------------------
GPU => temp=33.1'C
CPU => temp=32’C
\n

Hardware Assembly Process

\n

Below is a snapshot of the Geekworm NASPi parts out of the box (except for the Raspberry Pi 4B on the far right of the second row and the screwdriver in the lower right corner)

\n

The three key components in the second row, from left to right, are

\n
    \n
  • X-C1 V1.3 adapter board provides power management, interface adaptation, and security shutdown functions
  • \n
  • X823 V1.5 shield board provides a 2.5-inch SSD/HDD storage function (UASP supported)
  • \n
  • 4010 PWM fan and metal fan bracket
  • \n
\n

The assembly process was done step-by-step mainly by referring to NASPi installation video on Youtube, and the steps are generalized as follows.

\n
    \n
  1. Insert the SSD into the SATA III connector of X823, flip it to the other side, and fix it with screws.
  2. \n
  3. Install the Raspberry Pi 4B after fixing the spacers on this side, and place the 7-pin cable between the two
  4. \n
  5. Install the PWM fan on top of the Raspberry Pi 4B with the additional spacers
  6. \n
  7. Connect X-C1 and Raspberry Pi 4B, insert a 7-pin connector right to the X-C1 GPIO port and a 3-pin connector to the X-C1 FAN port
  8. \n
  9. Align and insert the 2x7-pin daughterboard to the GPIO port of the Raspberry Pi 4B and fix it with screws
  10. \n
  11. Plug in the USB 3.0 connector to connect the X823 USB 3.0 port to the corresponding Raspberry Pi 4B USB 3.0
  12. \n
\n

Now the installation of the internal accessories has been completed, we have a view of this

\n

\n

At this point, we added the USB-C power and pressed the front button to start the system, we could see the PWM fan started to spin. It was also observed that the fan spin rate was not constant, which demonstrated that the temperature controller PWM fan was working properly.

\n

The front button switch with embedded blue LED decides the whole system's on/off state and can be tested below

\n
    \n
  • Press the switch after power-on, and the system starts
  • \n
  • Press and hold the switch for 1-2 seconds while running, then the system restarts
  • \n
  • Press and hold the switch for 3 seconds during operation to shut down the system safely.
  • \n
  • Press and hold the switch for 7-8 seconds during operation to force shutdown
  • \n
\n

Running the off command on the SSH connection can also trigger a safe shutdown. Be cautious that we should not use the Linux shutdown command, as that would not power down the X-C1 board.

\n

After the button switch test, we now unplugged the USB 3.0 connector and inserted the entire module into the case. Next was to add the back panel and tighten the screws, then re-insert the USB 3.0 connector. This completed the whole NASPi storage kit assembly process. Below are the front and rear views of the final system provided by Geekworm (all interfaces and vents are marked).

\n

\n

OMV Installation and Configuration

\n

The third stage is for installing and configuring the key software package of the NAS system - PMV. The goal is to bring up the basic network file access service. Before restarting the NAS, we plugged a Seagate 2TB external HDD into the remaining USB 3.0 port. After booting, connected SSH to NASPi from macOS and performed the following process.

\n

Install OMV Package

\n

Installing OMV is as simple as running the following command line directly from a terminal with an SSH connection.

\n
wget -O - https://raw.githubusercontent.com/OpenMediaVault-Plugin-Developers/installScript/master/install | sudo bash
\n

Due to the large size of the entire OMV package, this installation process can take a long time. After the installation, the IP address of the system may change and you will need to reconnect to SSH at this time.

\n
(Reading database ... 51781 files and directories currently installed.)
Purging configuration files for dhcpcd5 (1:8.1.2-1+rpt3) ...
Purging configuration files for raspberrypi-net-mods (1.3.2) ...
Enable and start systemd-resolved ...
Unblocking wifi with rfkill ...
Adding eth0 to openmedivault database ...
IP address may change and you could lose connection if running this script via ssh.
client_loop: send disconnect: Broken pipe\t
\n

After reconnecting, you can use dpkg to view the OMV packages. As you can see, the latest version of OMV installed here is 6.0.5.

\n
pi@raspberrypi:~ $ dpkg -l | grep openme
ii openmediavault 6.0.5-1 all openmediavault - The open network attached storage solution
ii openmediavault-flashmemory 6.0.2 all folder2ram plugin for openmediavault
ii openmediavault-keyring 1.0 all GnuPG archive keys of the OpenMediaVault archive
ii openmediavault-omvextrasorg 6.0.4 all OMV-Extras.org Package Repositories for OpenMediaVault
\n

OMV Management UI

\n

At this point OMV's workbench is live. Launching a browser on a macOS computer and typing in the IP address will open the beautiful login screen (click on the 🌍 icon in the upper right corner to select the user interface language): After logging in with the default username and password shown above, you will see the Workbench screen. The first thing you should do at this point is to click the ⚙️ icon in the top right corner to bring up the settings menu and click \"Change Password\". You can also change the language here Clicking on \"Dashboard\" in the settings menu allows you to select the relevant components to be enabled. The menu on the left side provides task navigation for administrators and can be hidden when not needed. The complete OMV administration manual can be found in the online documentation

\n

Configure File Services

\n

Next is the key process for configuring the NAS, which consists of the following 5 steps.

\n
    \n
  1. Scan for mounted disk drives

    \n

    Click Storage > Disks from the sidebar menu to enter the hard drive management page. If there is an external USB storage device just plugged in, you can click 🔍 here to scan it out. The scan results for this system are as follows. The internal Samsung 500GB SSD and external Seagate 2TB HDD are detected, and the 32GB microSD that contains the entire software system is listed at the top:

    \n

    On the SSH terminal, we could see the information for the same set of mounted drivers

    \n

    pi@raspberrypi:~ $ df -h | grep disk
    /dev/sdb2 466G 13G 454G 3% /srv/dev-disk-by-uuid-D0604B68604B547E
    /dev/sda1 1.9T 131G 1.7T 7% /srv/dev-disk-by-uuid-DEB2474FB2472B7B

  2. \n
  3. Mount disk drive file systems

    \n

    Click Storage > File Systems from the sidebar menu to enter the file system management page. If the storage device does not have a file system yet, click ⨁ to Create or Mount the file system. OMV can create/mount ext4, ext3, JFS, and xfs file systems, but only mounts are supported for the NTFS file system. The following figure shows that OMV correctly mounts NTFS file systems for SSDs and HDDs:

  4. \n
  5. Set Shared Folders

    \n

    From the sidebar menu, click Storage > File Systems to access the shared folder management page. Here, click ⨁ to create a shared folder. When creating it, specify the name, corresponding file system, and relative path, and you can also add comments. Select the created folder and click the pencil icon again to edit the related information. This system sets the relative paths of shared folders Zixi-Primary and Zixi-Secondary for SSD and HDD respectively Notice the orange alert at the top of the figure above, which alerts the administrator that the configurations have changed and must click on the ✔️ icon to take effect.

  6. \n
  7. Add shared folder access users

    \n

    Click User Management > Users from the sidebar menu to enter the user management page. The system's default user pi has root privileges and cannot be used for file-sharing access due to security concerns. So you need to add a new user separately. On this page, click ⨁ to Create or Import user, only user name and password are required when creating a new user, others are optional. Once created, select this user and click the third folder+key icon (prompting \"Shared folder privileges\") to enter the following privileges settings page As shown in the figure, for this new user zixi, the administrator can set the read and write access permissions for each shared folder.

  8. \n
  9. Start file share services

    \n

    If you expand the \"Services\" item in the navigation menu, you can see that OMV manages five services: FTP, NFS, Rsync, SMB/CIFS, and SSH. SSH is enabled at the beginning of the system OS image preparation. NFS and SMB/CIFS are the most common network file-sharing protocols, and both are supported by macOS. Take SMB/CIFS as an example here. Click Services > SMB/CIFS from the sidebar menu to enter the management page. The page contains two buttons: Settings and Shares. Click \"Settings\" first to activate the SMB/CIFS service and configure the workgroup name on the new page, other options can be left as default. After saving, it returns to the SMB/CIFS administration page. Then enter \"Shares\", click ⨁ to Create shared folders Zixi-Primary and Zixi-Secondary on the new page then save. After that, click the ✔️ icon in the orange warning bar to make all configuration updates take effect, and you will end up with the following result

  10. \n
\n

Now our Raspberry Pi NAS system is ready for file sharing and the SMB/CIFS service is started. After checking the relevant components to turn on, our dashboard live monitoring looks like this

\n

Set Up Client Device

\n

Once the server side is ready, we need to add the network share folder on the client side as follows.

\n
    \n
  • Windows PC client\n
      \n
    • Open File Explore, click “This PC”
    • \n
    • Right-click on the blank area at the right pane, select \"Add a network location” on the popup menu
    • \n
    • Enter “\\\\<IP-address>\\” in the “Internet or network address\" input box
    • \n
    • Enter username and password when prompted
    • \n
  • \n
  • MacBook client (screenshot below)\n
      \n
    • Open Finder, click the menu item Go
    • \n
    • Click “Connect to Server...”
    • \n
    • Enter URL “smb://<IP-address>/”, then click Connect
    • \n
    • Enter username and password when prompted
      \n
    • \n
  • \n
\n

Once the client side is set up, users can perform various operations on the network share folder as if it were a local directory, such as previewing, creating new, opening or copying files, creating new subdirectories, or deleting existing subdirectories.

\n

Plex Installation and Configuration

\n

The last stage is to install and configure the Plex Media Server, and then start a network streaming service.

\n

Install Media Server

\n

The process of installing Plex Media Server requires HTTPS transport support, so we must first install the https-transport package. SSH to our Raspberry Pi NAS and execute the install command

\n
sudo apt-get install apt-transport-https
\n

Next add the Plex repository to the system, which requires downloading the Plex sign key first. Here are the related commands and run logs

\n
pi@raspberrypi:~ $ curl https://downloads.plex.tv/plex-keys/PlexSign.key | sudo apt-key add -
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).
100 3072 100 3072 0 0 10039 0 --:--:-- --:--:-- --:--:-- 10039
OK
\n

Use the same apt-key command to check the newly added Plex sign key

\n
pi@raspberrypi:~ $ apt-key list
Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).
/etc/apt/trusted.gpg
...
pub rsa4096 2015-03-22 [SC]
CD66 5CBA 0E2F 88B7 373F 7CB9 9720 3C7B 3ADC A79D
uid [ unknown] Plex Inc.
sub rsa4096 2015-03-22 [E]
...
\n

You can see that Plex uses 4096-bit RSA keys. For the warning message \"apt-key is deprecated...\" in the above log, you can ignore it for now. Go to read some discussion on the askubuntu forum if you are interested.

\n

The next step is to add the Plex repository to the system repository list, and then update the packages

echo deb https://downloads.plex.tv/repo/deb public main | sudo tee /etc/apt/sources.list.d/plexmediaserver.list
sudo apt-get update
Now we can start the actual Plex Media Server installation with the following installation commands

\n
pi@raspberrypi:~ $ sudo apt install plexmediaserver
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
plexmediaserver
0 upgraded, 1 newly installed, 0 to remove and 20 not upgraded.
Need to get 66.1 MB of archives.
After this operation, 146 MB of additional disk space will be used.
Get:1 https://downloads.plex.tv/repo/deb public/main armhf plexmediaserver armhf 1.25.0.5282-2edd3c44d [66.1 MB]
Fetched 66.1 MB in 28s (2392 kB/s)
Selecting previously unselected package plexmediaserver.
(Reading database ... 51783 files and directories currently installed.)
Preparing to unpack .../plexmediaserver_1.25.0.5282-2edd3c44d_armhf.deb ...
PlexMediaServer install: Pre-installation Validation.
PlexMediaServer install: Pre-installation Validation complete.
Unpacking plexmediaserver (1.25.0.5282-2edd3c44d) ...
Setting up plexmediaserver (1.25.0.5282-2edd3c44d) ...

Configuration file '/etc/apt/sources.list.d/plexmediaserver.list'
==> File on system created by you or by a script.
==> File also in package provided by package maintainer.
What would you like to do about it ? Your options are:
Y or I : install the package maintainer's version
N or O : keep your currently-installed version
D : show the differences between the versions
Z : start a shell to examine the situation
The default action is to keep your current version.
*** plexmediaserver.list (Y/I/N/O/D/Z) [default=N] ?
PlexMediaServer install: PlexMediaServer-1.25.0.5282-2edd3c44d - Installation starting.
PlexMediaServer install:
PlexMediaServer install: Now installing based on:
PlexMediaServer install: Installation Type: New
PlexMediaServer install: Process Control: systemd
PlexMediaServer install: Plex User: plex
PlexMediaServer install: Plex Group: plex
PlexMediaServer install: Video Group: video
PlexMediaServer install: Metadata Dir: /var/lib/plexmediaserver/Library/Application Support
PlexMediaServer install: Temp Directory: /tmp
PlexMediaServer install: Lang Encoding: en_US.UTF-8
PlexMediaServer install: Nvidia GPU card: Not Found
PlexMediaServer install:
PlexMediaServer install: Completing final configuration.
Created symlink /etc/systemd/system/multi-user.target.wants/plexmediaserver.service → /lib/systemd/system/plexmediaserver.service.
PlexMediaServer install: PlexMediaServer-1.25.0.5282-2edd3c44d - Installation successful. Errors: 0, Warnings: 0
\n

The log shows a question is asked about the Plex media server list (plexmediaserver.list), just choose the default N. When we see \"Installation successful\", we know that the installation was successful. At this point, the Plex streaming service is up and running. Invoking the Nmap scan again from the macOS side, we find that TCP port 32400 for Plex service is open.

\n
❯ nmap -p1-65535 192.168.2.4 | grep open
22/tcp open ssh
80/tcp open http
111/tcp open rpcbind
139/tcp open netbios-ssn
445/tcp open microsoft-ds
2049/tcp open nfs
5357/tcp open wsdapi
32400/tcp open plex
\n

Configure Media Server

\n

The configuration of the Plex Media Server has been done on the web GUI. Launch a browser on the macOS computer and type in the URL http://<IP-address>:32400/web, now we can see the following page if no surprise We can sign in with a Google, Facebook, or Apple account, or we can enter an email to create a new account. Follow the instructions on the page step by step, no need for any payment, soon we reach the Server Setup page. Here we can configure the server name and add libraries. Normally we don't need to access our home media server from outside, so remember to uncheck the \"Allow me to access my media outside my home\" box in this step. To add a library, first select the type of library (movies, TV episodes, music, photos, etc.), then click the \"BROWSE FOR MEDIA FOLDER\" button to browse and select the corresponding folder. Once the library is added, the included media files will immediately appear in the local service directory, as shown in the screenshot below Here we have a local server named ZIXI-RPI-NAS for our Raspberry Pi NAS, the movie directory in the library shows The Matrix trilogy and is playing the first one The Matrix. Move your mouse over the server name and ➕ icon will appear to the right, click on it to continue adding new media libraries.

\n

Once the Plex Media Server is configured, we can open a browser from any device on our home network to do streaming on-demand, without the need to download additional applications. The whole experience is just like our own proprietary home Netflix service. This is awesome!

\n

Performance Review

\n

By connecting a macOS laptop to one of the remaining ports of the TL-SG105, we could perform some simple same-subnet tests to evaluate the performance of this NAS system fully.

\n

System Stress Test

\n

Referring to Geekworm NASPi Stress Test Wiki page, we executed the following command over SSH connection, which cloned the test script from GitHub and ran the stress test:

\n
git clone https://github.com/geekworm-com/rpi-cpu-stress
cd rpi-cpu-stress
chmod +x stress.sh
sudo ./stress.sh
\n

Simultaneously we established a second SSH session and ran htop to monitor system status. The screenshot below was taken while close to the 5-minute mark (left is the htop real-time display, and right is the stress test output) Dividing the temp value on the right side by 1000 gave the CPU temperature. All 4 CPU cores reached 100% full load during the test, while the maximum temperature did not exceed 70°C. At this moment, there was no obvious heat sensation when touching the case. Typing ctrl-c to stop the stress test, and then executing the temperature measurement script again

\n
pi@raspberrypi:~ $ ./pi-temp.sh
Fri Dec 24 15:59:21 PST 2021 @ raspberrypi
-------------------------------------------
GPU => temp=39.9'C
CPU => temp=40'C
\n

The system temperature returned to a low range value. This test result assures the system meets the design goal.

\n

File Transfer Speed Test

\n

The file transfer speed can be roughly measured with the secure remote copy tool SCP. First, create a 1GB size file by running the mkfile command on the macOS client, then copy it to the user directory of the remote NAS system with the scp command

\n
❯ mkfile 1G test-nas.dmg
ls -al test-nas.dmg
rw------- 1 sxiao staff 1073741824 Dec 19 20:53 test-nas.dmg
❯ scp test-nas.dmg pi@192.168.2.4:/home/pi/
pi@192.168.2.4's password:
test-nas.dmg 100% 1024MB 19.2MB/s 00:53
\n

After the copy was done, it would print the time spent and the deduced speed. Running the command with the source and the destination reversed would give us the speed of receiving a file from the NAS system.

\n
❯ scp pi@192.168.2.4:/home/pi/test-nas.dmg test-nas-rx.dmg
pi@192.168.2.4's password:
test-nas.dmg 100% 1024MB 65.7MB/s 00:15
\n

Repeated 3 times and got the results listed below

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Transfor TypeServer OperationTime (s)Speed (MB/s)
SendWrite5319.2
SendWrite4522.5
SendWrite5020.4
ReceiveRead1565.7
ReceiveRead1660.3
ReceiveRead1566.3
\n

As can be seen, the speed of remote write is around 20MB/s, while the speed of remote file read can reach over 60MB/s. Considering that scp-related encryption and decryption are implemented in software on general-purpose Raspberry Pi systems, this result should be considered passable.

\n

Disk Access Speed Test

\n

The real test of the NAS's performance is the network drive read/write speed test. For this, we downloaded the AmorphousDiskMark app from Apple's App Store. This is an easy and efficient drive speed test that measures the read/write performance of a storage device in terms of MB/s and IOPS (input/output operations per second). It has four types of tests:

\n
    \n
  1. sequential read/write, 1MB block, queue depth 8
  2. \n
  3. sequential read/write, 1MB block, queue depth 1
  4. \n
  5. random read/write, 4KB block, queue depth 64
  6. \n
  7. random read/write, 4KB block, queue depth 1
  8. \n
\n

The above queue depths are the default values, but other values are also available. In addition, users can also modify the test file size and duration.

\n

Run the application on the macOS client and select the remote SMB folders Zixi-Primary (Samsung SSD) and Zixi-Secondary (Seagate HDD) respectively at the top, then click the All button in the upper left corner to start the NAS drive speed test process. A side-by-side comparison of the two test results is shown below

\n
\n

This gives a few observations:

\n
    \n
  • Reads are faster than writes for NAS drives, and the difference under random access is huge.
  • \n
  • SSD outperforms HDD for both sequential and random accesses.
  • \n
  • Large queue depth speeds up reads, especially for random accesses, but there is little impact on writes.
  • \n
  • For both SSDs and HDDs, sequential reads/writes are significantly more efficient than random reads/writes.
  • \n
  • For both SSDs and HDDs, sequential reads/writes reach their highest speeds at large queue depths.
  • \n
\n

These are not surprising and are consistent with the test results on macOS laptops with direct external SSDs and HDDs, only with the lower numbers. With this NAS system, both the SSD and HDD are connected via the USB 3.0 interface. USB 3.0 supports transfer speeds of up to 5Gbit/s, so the performance bottleneck of the system is the network interface bandwidth and processor power.

\n

That being said, for both SSDs and HDDs, the transfer speeds have been more than 900Mbit/s at 1MB sequential read and queue depth 8, close to the upper bandwidth limit of the Gigabit Ethernet interface. This read speed can support a single 1080p60 video stream at a frame rate of 60fps or 2 parallel 1080i50 video streams at a frame rate of 25fps, which is sufficient for home streaming services. In another media service test, the NAS system performs satisfactorily with three computers playing HD video on demand and one phone playing MP3 music without any lag.

\n

Project Summary

\n

This completes our Raspberry Pi home NAS project. Now we can move our NAS to a more permanent location to provide network file and streaming services for the whole family.

\n

\n

Economically, our home NAS has the cost summarized in the table below (excluding SSD/HDD)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
DevicesFunctionsCost($)
Raspberry Pi 4B 2/4/8GB RAMPrimary hardware system45/55/75
Samsung 32GB EVO+ Class-10 Micro SDHCOS storage10
Geekworm NASPi Raspberry Pi 4B NAS Storage KitCase, extending board and PWM fan60
Geekworm 20W 5V 4A USB-C Power AdaptorPower supply15
TP-Link TL-SG105 5-Port Gigabit Ethernet SwitchDesktop switch15
\n

Even with the choice of 8GB RAM Raspberry Pi 4B, the whole cost is only $175, a little more than half of the price of the low-end brand NAS sold in the market. Unless there are a lot of client devices that need streaming services, the memory consumption is usually under 2GB, so the 2GB Raspberry Pi 4B should be able to work in most home scenarios. That cuts the cost down to $145, less than half the MSRP.

\n

On the other hand, this DIY project was a very good exercise of hands-on practice, helping us gain valuable intuitive experience in building network connections, configuring system hardware and software, and tuning and testing application layer services. To sum up, the home NAS system built with Raspberry Pi 4B and OMV, combined with a Plex media server, provides a cost-effective solution for file backup and streaming media services in the home network.

\n

Appendix: List of related devices and Amazon links

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n

CanaKit Raspberry Pi 4B 8GB RAM + 128GB MicroSD Extrem Kit https://amzn.to/3DUeDfm
\nSamsung 32GB EVO+ Class 10 Micro SDHC with Adapter https://amzn.to/3FLkTb7
\nGeekworm NASPi 2.5\" SATA HDD/SSD Raspberry Pi 4B NAS Storage Kit https://amzn.to/3m5djAi
\nGeekworm Raspberry Pi 4 20W 5V 4A USB-C Power Adaptor https://amzn.to/3m1EXOf
\nTP-Link TL-SG105 5-Port Gigabit Ethernet Switch https://amzn.to/3pRkBsi
\nSamsung 870 EVO 500GB 2.5\" SATA III Internal SSD https://amzn.to/3DPKnCl
\nSeagate Portable 2TB USB 3.0 External HDD https://amzn.to/3EYegl4
\nSynology 2-Bay 2GB NAS DiskStation DS220+ https://amzn.to/3Jp5qjd
\nSynology 5-Bay 8GB NAS DiskStation DS1520+ https://amzn.to/3qniQDm

\n
\n","categories":["DIY Projects"],"tags":["Raspberry Pi","NAS"]},{"title":"RSA: Attack and Defense (II)","url":"/en/2023/11/17/RSA-attack-defense-2/","content":"

This article first supplements two specific integer factorization methods - Fermat's factorization method and Pollard's rho algorithm, explaining the essence of their algorithms and applicable scenarios, and provides a Python reference implementation. Next, it analyzes in detail a classic low private exponent attack - Wiener's attack, elaborating on the mathematical basis, the attack principle, and the attack procedure, with a complete Python program. The article also cites the latest research paper proposing a new upper bound for the private exponent when Wiener's attack is successful and verifies the correctness of this limit with a test case.

\n

The enemy knows the system being used.
Claude Shannon (American mathematician, electrical engineer, computer scientist, and cryptographer known as the \"father of information theory\".)

\n
\n

Previous article: RSA: Attack and Defense (I)

\n

Integer Factorization (Supplementary)

\n

Even if the RSA modulus \\(N\\) is a very big number (with sufficient bits), problems can still arise if the gap between the prime factors \\(p\\) and \\(q\\) is too small or too large. In such cases, there are specific factorization algorithms that can effectively retrieve p and q from the public modulus N.

\n

Fermat's Factorization Method

\n

When the prime factors \\(p\\) and \\(q\\) are very close, Fermat's factorization method can factorize the modulus N in a very short time. Fermat's factorization method is named after the French mathematician Pierre de Fermat. Its base point is that every odd integer can be represented as the difference between two squares, i.e. \\[N=a^2-b^2\\] Applying algebraic factorization on the right side yields \\((a+b)(a-b)\\). If neither factor is one, it is a nontrivial factor of \\(N\\). For the RSA modulus \\(N\\), assuming \\(p>q\\), correspondingly \\(p=a+b\\) and \\(q=a-b\\). In turn, it can be deduced that \\[N=\\left({\\frac {p+q}{2}}\\right)^{2}-\\left({\\frac {p-q}{2}}\\right)^{2}\\] The idea of Fermat's factorization method is to start from \\(\\lceil{\\sqrt N}\\rceil\\) and try successive values of a, then verify if \\(a^{2}-N=b^{2}\\). If it is true, the two nontrivial factors \\(p\\) and \\(q\\) are found. The number of steps required by this method is approximately \\[{\\frac{p+q}{2}}-{\\sqrt N}=\\frac{({\\sqrt p}-{\\sqrt q})^{2}}{2}=\\frac{({\\sqrt N}-q)^{2}}{2q}\\] In general, Fermat's factorization method is not much better than trial division. In the worst case, it may be slower. However, when the difference between \\(p\\) and \\(q\\) is not large, and \\(q\\) is very close to \\(\\sqrt N\\), the number of steps becomes very small. In the extreme case, if the difference between \\(q\\) and \\(\\sqrt N\\) is less than \\({\\left(4N\\right)}^{\\frac 1 4}\\), this method only takes one step to finish.

\n

Below is a Python implementation of Fermat's factorization method, and an example of applying it to factorize the RSA modulus N:

\n
import gmpy2
import time

def FermatFactor(n):
assert n % 2 != 0

a = gmpy2.isqrt(n) + 1
b2 = gmpy2.square(a) - n

while not gmpy2.is_square(b2):
a += 1
b2 = gmpy2.square(a) - n

b = gmpy2.isqrt(b2)
return a + b, a - b

p = 7422236843002619998657542152935407597465626963556444983366482781089760760914403641211700959458736191688739694068306773186013683526913015038631710959988771
q = 7422236843002619998657542152935407597465626963556444983366482781089760759017266051147512413638949173306397011800331344424158682304439958652982994939276427
N = p * q
print("N =", N)

start = time.process_time()
(p1, q1) = FermatFactor(N)
end = time.process_time()
print(f'Elapsed time {end - start:.3f}s.')

assert(p == p1)
assert(q == q1)
\n

The FermatFactor() function defined at the beginning of the program implements the Fermat factorization method. It calls three library functions of gmpy2: isqrt() to find the square root of an integer, square() to execute the squaring operation, and is_square() to verify if the input is a square number. Two large prime numbers \\(p\\) and \\(q\\) of 154 decimal digits each are defined later, and multiplying them gives \\(N\\). Then \\(N\\) is fed into the FermatFactor() function and the program starts timing. When the function returns, it prints the elapsed time and confirms the factorization.

\n
N = 55089599753625499150129246679078411260946554356961748980861372828434789664694269460953507615455541204658984798121874916511031276020889949113155608279765385693784204971246654484161179832345357692487854383961212865469152326807704510472371156179457167612793412416133943976901478047318514990960333355366785001217
Elapsed time 27.830s.
\n

As can be seen, in less than half a minute, this large number of 308 decimal digits (about 1024 bits) was successfully factorized! Going back and examining \\(p\\) and \\(q\\), one can see that the first 71 digits of these two large prime numbers of 154 decimal digits are exactly the same. This is exactly the scenario in which the Fermat factorization method exerts its power. If you simply modify the FermatFactor() function to save the starting \\(a\\) value and compare it to the value at the end of the loop, you get a loop count of 60613989. With such a small number value, it's no wonder that the factorization is done so quickly.

\n

Therefore, the choice of the large prime numbers \\(p\\) and \\(q\\) must not only be random but also be far enough apart. After obtaining two large prime numbers, the difference between them shall be checked. If it is too small, regeneration is required to prevent attackers from using Fermat's factorization method to crack it.

\n

Pollard's Rho Algorithm

\n

On the opposite end, if the gap between the large prime factors \\(p\\) and \\(q\\) is too large, they may be cracked by Pollard's rho algorithm. This algorithm was invented by British mathematician John Pollard1 in 1975. It requires only a small amount of storage space, and its expected running time is proportional to the square root of the smallest prime factor of the composite number being factorized.

\n

The core idea of Pollard's rho algorithm is to use the collision pattern of traversal sequences to search for factors, and its stochastic and recursive nature allows it to factorize integers efficiently in relatively low complexity. First, for \\(N=pq\\), assume that \\(p\\) is the smaller nontrivial factor. The algorithm defines a polynomial modulo \\(N\\) \\[f(x)=(x^{2}+c){\\pmod N}\\] A pseudorandom sequence can be generated by making recursive calls with this polynomial, and the sequence generation formula is \\(x_{n+1}=f(x_n)\\). For example, given an initial value of \\(x_0=2\\) and a constant \\(c=1\\), it follows that \\[\\begin{align}\nx_1&=f(2)=5\\\\\nx_2&=f(x_1)=f(f(2))=26\\\\\nx_3&=f(x_2)=f(f(f(2)))=677\\\\\n\\end{align}\\] For two numbers \\(x_i\\) and \\(x_j\\) in the generated sequence, \\(|x_i-x_j|\\) must be a multiple of \\(p\\) if \\(x_i\\neq x_j\\) and \\(x_i\\equiv x_j{\\pmod p}\\). In this case, calculating \\(\\gcd(|x_i-x_j|,N)\\) results in \\(p\\). Based on the Birthday Paradox, in the worst case, it is expected that after generating about \\(\\sqrt p\\) numbers, there will be two numbers that are the same under the modulus \\(p\\), thus successfully factorizing \\(N\\). However, the time complexity of performing pairwise comparisons is still unsatisfactory. In addition, storing so many numbers is also troublesome when N is large.

\n

How to solve these problems? This is where the ingenuity of Pollard's rho algorithm lies. Pollard found that the sequence generated by this pseudorandom number generator has two properties:

\n
    \n
  1. Since each number depends only on the value that precedes it, and the numbers generated under the modular operation are finite, sooner or later it will enter a cycle. As shown below, the resulting sequence will eventually form a directed graph similar in shape to the Greek letter \\(\\rho\\), from which the algorithm takes its name. \"Cycle
  2. \n
  3. When \\(|x_i-x_j| \\equiv 0 \\pmod p\\), there must be \\[|f(x_i)-f(x_j)|=|{x_i}^2-{x_j}^2|=|x_i+x_j|\\cdot|x_i-x_j|\\equiv 0 \\pmod p\\] This shows that if two numbers in the sequence satisfy a certain condition under modulus operation, all equally spaced pairs of numbers satisfy the same condition.
  4. \n
\n

Insightful of these two properties, Pollard utilizes Floyd's cycle-finding algorithm (also known as the tortoise and hare algorithm) to set up the fast and slow nodes \\(x_h\\) and \\(x_t\\). Starting from the same initial value \\(x_0\\), the slow node \\(x_t\\) moves to the next node in the sequence every step, while the fast node \\(x_h\\) moves forward by two nodes at a time, i.e. \\[\\begin{align}\nx_t&=f(x_t)\\\\\nx_h&=f(f(x_h))\\\\\n\\end{align}\\] After that, calculate \\(\\gcd(|x_h-x_t|,N)\\), and the result that is greater than 1 and less than \\(N\\) is \\(p\\), otherwise continue with the same steps. With this design, since each move is equivalent to checking a new node spacing, pairwise comparisons are unnecessary. If not found, eventually the fast and slow nodes will meet on the cycle, at which time the result of finding the greatest common divisor is \\(N\\). The algorithm's recommendation at this point is to exit and regenerate the pseudorandom number sequence with a different initial value or constant \\(c\\) and try again.

\n

This is the classic Pollard's rho algorithm. Its time complexity is \\(𝑂(\\sqrt p\\log N)\\) (\\(\\log\\) comes from the required \\(\\gcd\\) operations). For RSA modulus \\(N\\), obviously \\(p\\leq \\sqrt N\\), so the upper bound on the time complexity can be written as \\(𝑂(N^{\\frac 1 4}\\log N)\\). The time complexity expression for Pollard's rho algorithm indicates that the smaller the minimum prime factor of the composite number being factorized, the faster the factorization is expected to be. An excessively small \\(p\\) is extremely unsafe.

\n

Programming Pollard's rho algorithm is not difficult. The following Python code shows a function implementation of the algorithm, PollardRhoFactor(), and some test cases

\n
import gmpy2
import time

def PollardRhoFactor(n, seed, c):

if n % 2 == 0: return 2
if gmpy2.is_prime(n): return n

while True:
f = lambda x: (x**2 + c) % n
t = h = seed
d = 1

while d == 1:
t = f(t) # Tortoise
h = f(f(h)) # Hare
d = gmpy2.gcd(h - t, n)

if d != n:
return d # find a non-trivial factor

# start a new round with updated seed and c
seed = h
c += 1

N = [10967535067, 18446744073709551617, 97546105601219326301,
780002082420246798979794021150335143]

print(f"{'N':<37}{'P':<16}{'Elapsed Time (s)':}")
for i in range(0, len(N)):
start = time.process_time()
p = PollardRhoFactor(N[i], 2, 1)
end = time.process_time()
print(f'{N[i]:<37}{p:<16}{end - start:16.3f}')

F8 = 2**(2**8) + 1 # A 78-digit Fermat number
start = time.process_time()
p = PollardRhoFactor(F8, 2, 1)
end = time.process_time()
print(f'\\nF8 = {F8}\\np = {p}\\nElapsed time {end - start:.3f}s')
\n

The function PollardRhoFactor() accepts three arguments: n is the composite number to be factorized, seed is the initial value of the pseudorandom sequence, and c is the constant value in the generating polynomial. The function internally uses two while to form a double loop: inside the outer loop defines the generating polynomial f and the fast and slow nodes h and t, while the node moving steps and the greatest common divisor operation are implemented in the inner loop. The inner loop ends only if the greatest common divisor d is not 1. At this point, if d is not equal to n, the function returns the non-trivial factor d. Otherwise, d equals n, meaning the fast and slow nodes have met on the cycle. In this situation, the code in the outer loop resets seed to the value of the fast node and increments c, thus restarting a new round of search.

\n

Running the above code on a MacBook Pro (2019), the output is as follows

\n
N                                    P               Elapsed Time (s)
10967535067 104729 0.001
18446744073709551617 274177 0.002
97546105601219326301 9876543191 0.132
780002082420246798979794021150335143 244300526707007 6.124

F8 = 115792089237316195423570985008687907853269984665640564039457584007913129639937
p = 1238926361552897
Elapsed time 64.411s
\n

This result proves the effectiveness of Pollard's rho algorithm. In particular, for the last test, the input to the function was the Fermat number \\(F_8\\) (defined as \\(F_{n}=2^{2^{n}}+1\\), where \\(n\\) is a non-negative integer). In 1980, Pollard and Australian mathematician Richard Brent 2 working together applied this algorithm to factorize \\(F_8\\) for the first time. The factorization took 2 hours on a UNIVAC 1100/42 computer. And now, on a commercial off-the-shelf laptop computer, Pollard's rho algorithm revealed the smaller prime factor 1238926361552897 of \\(F_8\\) in 64.4 seconds.

\n

Subsequently, Pollard and Brent made further improvements to the algorithm. They observed that if \\(\\gcd(d, N)>1\\), for any positive integer \\(k\\), there is also \\(\\gcd(kd, N)>1\\). So multiplying \\(k\\) consecutive \\((|x_h-x_t| \\pmod N)\\) and taking the modulo \\(N\\) with the product, and then solving for the greatest common divisor with \\(N\\) should obtain the same result. This method replaces \\(k\\) times \\(\\gcd\\) with \\((k-1)\\) times multiplications modulo \\(N\\) and a single \\(\\gcd\\), thus achieving acceleration. The downside is that occasionally it may cause the algorithm to fail by introducing a repeated factor. When this happens, it then suffices to reset \\(k\\) to 1 and fall back to the regular Pollard's rho algorithm.

\n

The following Python function implements the improved Pollard's rho algorithm. It adds an extra for loop to implement the multiplication of \\(k\\) consecutive differences modulo \\(N\\), with the resulting product stored in the variable mult. mult is fed to the greatest common divisor function with \\(N\\), and the result is assigned to d for further check. If this fails, \\(k\\) is set to 1 in the outer loop.

\n
def PollardRhoFactor2(n, seed, c, k):

if n % 2 == 0: return 2
if gmpy2.is_prime(n): return n

while True:
f = lambda x: (x**2 + c) % n
t = h = seed
d = 1

while d == 1:
mult = 1
for _ in range(k):
t = f(t) # Tortoise
h = f(f(h)) # Hare
mult = (mult * abs(h - t)) % n

d = gmpy2.gcd(mult, n)

if d != n:
return d # find a non-trivial factor

# start a new round with updated seed and c
seed = h
c += 1
k = 1 # fall back to regular rho algorithm

print(f"{'N':<37}{'P':<16}{'Elapsed Time (s)':}")
for i in range(0, len(N)):
start = time.process_time()
p = PollardRhoFactor2(N[i], 2, 1, 100)
end = time.process_time()
print(f'{N[i]:<37}{p:<16}{end - start:16.3f}')

F8 = 2**(2**8) + 1 # A 78-digit Fermat number
start = time.process_time()
p = PollardRhoFactor2(F8, 2, 1, 100)
end = time.process_time()
print(f'\\nF8 = {F8}\\np = {p}\\nElapsed time {end - start:.3f}s')
\n

Using the same test case, called with \\(k\\) set to 100, the program runs as follows

\n
N                                    P               Elapsed Time (s)
10967535067 104729 0.001
18446744073709551617 274177 0.002
97546105601219326301 9876543191 0.128
780002082420246798979794021150335143 244300526707007 5.854

F8 = 115792089237316195423570985008687907853269984665640564039457584007913129639937
p = 1238926361552897
Elapsed time 46.601s
\n

It can be seen that for relatively small composite \\(N\\), the improvement is not significant. As \\(N\\) becomes larger, the speedup is noticeable. For the 78-bit decimal Fermat number \\(F_8\\), the improved Pollard's rho algorithm takes only 46.6 seconds, which is a speedup of more than 27% over the regular algorithm. The improved Pollard \\(\\rho\\) algorithm indeed brings significant speedup.

\n

To summarize the above analysis, implementation, and testing of Pollard's rho algorithm, it is necessary to set a numerical lower bound for the generated prime numbers \\(p\\) and \\(q\\) to be used by RSA. If either of them is too small, it must be regenerated or it may be cracked by an attacker applying Pollard's rho algorithm.

\n

Low Private Exponent Attack

\n

For some particular application scenarios (e.g., smart cards and IoT), limited by the computational capability and low-power requirements of the device, a smaller value of private exponent \\(d\\) is favored for fast decryption or digital signing. However, a very low private exponent is very dangerous, and there are some clever attacks that can totally breach such an RSA cryptosystem.

\n

Wiener's Attack

\n

In 1990, Canadian cryptographer Michael J. Wiener conceived an attack scheme3 based on continued fraction approximation that can effectively recover the private exponent \\(d\\) from the RSA public key \\((N, e)\\) under certain conditions. Before explaining how this attack works, it is important to briefly introduce the concept and key properties of continued fraction.

\n

Continued Fraction

\n

The continuous fraction itself is just a mathematical expression, but it introduces a new perspective on the study of real numbers. The following is a typical continued fraction \\[x = a_0 + \\cfrac{1}{a_1 + \\cfrac{1}{a_2 + \\cfrac{1}{\\ddots\\,}}}\\] where \\(a_{0}\\) is an integer and all other \\(a_{i}(i=1,\\ldots ,n)\\) are positive integers. One can abbreviate the continued fraction as \\(x=[a_0;a_1,a_2,\\ldots,a_n]\\). Continued fractions have the following properties:

\n
    \n
  1. Every rational number can be expressed as a finite continued fraction, i.e., a finite number of \\(a_{i}\\). Every rational number has an essentially unique simple continued fraction representation with infinite terms. Here are two examples: \\[\\begin{align}\n\\frac {68} {75}​&=0+\\cfrac {1} {1+\\cfrac {1} {\\small 9+\\cfrac {1} {\\scriptsize 1+\\cfrac {1} {2+\\cfrac {1} {2}}}}}=[0;1,9,1,2,2]\\\\\nπ&=[3;7,15,1,292,1,1,1,2,…]\n\\end{align}\\]

  2. \n
  3. To calculate the continued fraction representation of a positive rational number \\(f\\), first subtract the integer part of \\(f\\), then find the reciprocal of the difference and repeat till the difference is zero. Let \\(a_i\\) be the integer quotient, \\(r_i\\) be the difference of the \\(i\\)th step, and \\(n\\) be the number of steps, then \\[\\begin{align}\na_0 &= \\lfloor f \\rfloor, &r_0 &= f - a_0\\\\\na_i&={\\large\\lfloor} \\frac 1 {r_{i-1}} {\\large\\rfloor}, &r_i &=\\frac 1 {r_{i-1}} - a_i \\quad (i = 1, 2, ..., n)\\\\\n\\end{align}\\] The corresponding Python function implementing the continued fraction expansion of rationals is as follows

    \n

    def cf_expansion(nm: int, dn:int) -> list:
    """ Continued Fraction Expansion of Rationals
    Parameters:
    nm - nominator
    dn - denomainator
    Return:
    List for the abbreviated notation of the continued fraction
    """
    cf = []
    a, r = nm // dn, nm % dn
    cf.append(a)

    while r != 0:
    nm, dn = dn, r
    a = nm // dn
    r = nm % dn
    cf.append(a)

    return cf

  4. \n
  5. For both rational and irrational numbers, the initial segments of their continued fraction representations produce increasingly accurate rational approximations. These rational numbers are called the convergents of the continued fraction. The even convergents continually increase, but are always less than the original number; while the odd ones continually decrease, but are always greater than the original number. Denote the numerator and denominator of the \\(i\\)-th convergent as \\(h_i\\) and \\(k_i\\) respectively, and define \\(h_{-1}=1,h_{-2}=0\\) and \\(k_{-1}=0,k_{-2}=1\\), then the recursive formula for calculating the convergents is \\[\\begin{align}\n\\frac {h_0} {k_0} &= [0] = \\frac 0 1 = 0<\\frac {68} {75}\\\\\n\\frac {h_1} {k_1} &= [0;1] = \\frac 1 1 = 1>\\frac {68} {75}\\\\\n\\frac {h_2} {k_2} &= [0;1,9] = \\frac 9 {10}<\\frac {68} {75}\\\\\n\\frac {h_3} {k_3} &= [0;1,9,1] = \\frac {10} {11}>\\frac {68} {75}\\\\\n\\frac {h_4} {k_4} &= [0;1,9,1,2] = \\frac {29} {32}<\\frac {68} {75}\\\\\n\\end{align}\\] It can be verified that these convergents satisfy the aforementioned property and are getting closer to the true value. The following Python function implements a convergent generator for a given concatenated fraction expansion, and it returns a tuple of objects consisting of the convergent's numerator and denominator.

    \n

    def cf_convergent(cf: list) -> (int, int):
    """ Calculates the convergents of a continued fraction
    Parameters:
    cf - list for the continued fraction expansion
    Return:
    A generator object of the convergent tuple
    (numerator, denominator)
    """
    nm = [] # Numerator
    dn = [] # Denominators

    for i in range(len(cf)):
    if i == 0:
    ni, di = cf[i], 1
    elif i == 1:
    ni, di = cf[i]*cf[i-1] + 1, cf[i]
    else: # i > 1
    ni = cf[i]*nm[i-1] + nm[i-2]
    di = cf[i]*dn[i-1] + dn[i-2]

    nm.append(ni)
    dn.append(di)
    yield ni, di

  6. \n
  7. Regarding the convergents of continued fractions, there is also an important Legendre4 theorem: Let \\(a∈ \\mathbb Z, b ∈ \\mathbb Z^+\\) such that \\[\\left\\lvert\\,f - \\frac a b\\right\\rvert< \\frac 1 {2b^2}\\] then \\(\\frac a b\\) is a convergent of the continued fraction of \\(f\\).

  8. \n
\n

Attack Mechanism

\n

Now analyze how Wiener's attack works. From the relationship between RSA public and private exponent \\(ed\\equiv 1 {\\pmod {\\varphi(N)}}\\), it can be deduced that there exists an integer \\(k\\) such that \\[ed - k\\varphi(N) = 1\\] Dividing both sides by \\(d\\varphi(N)\\) gives \\[\\left\\lvert\\frac e {\\varphi(N)} - \\frac k d\\right\\rvert = \\frac 1 {d{\\varphi(N)}}\\] Careful observation of this formula reveals that because \\(\\varphi(N)\\) itself is very large, and \\(\\gcd(k,d)=1\\), \\(\\frac k d\\) is very close to \\(\\frac e {\\varphi(N)}\\). In addition, \\[\\varphi(N)=(p-1)(q-1)=N-(p+q)+1\\] Its difference from \\(N\\) is also relatively small. Therefore, \\(\\frac k d\\) and \\(\\frac e N\\) also do not differ by much. Since RSA's \\((N,e)\\) are public, Wiener boldly conceived - if \\(\\pmb{\\frac e N}\\) is expanded into a continued fraction, it is possible that \\(\\pmb{\\frac k d}\\) is one of its convergents!

\n

So how to verify if a certain convergent is indeed \\(\\frac k d\\)? With \\(k\\) and \\(d\\), \\(\\varphi (N)\\) can be calculated, thereby obtaining \\((p+q)\\). Since both \\((p+q)\\) and \\(pq\\) are known, constructing a simple quadratic equation5 can solve for \\(p\\) and \\(q\\). If their product equals \\(N\\), then \\(k\\) and \\(d\\) are correct and the attack succeeds.

\n

What are the conditions for Wiener's attack to work? Referring to Legendre's theorem mentioned above, it can be deduced that if \\[\\left\\lvert\\frac e N - \\frac k d\\right\\rvert < \\frac 1 {2{d^2}}\\] then \\(\\frac k d\\) must be a convergent of \\(\\frac e N\\). This formula can also be used to derive an upper bound of the private exponent d for a feasible attack. Wiener's original paper states the upper bound as \\(N^{\\frac 1 4}\\), but without detailed analysis. In 1999, American cryptographer Dan Boneh6 provided the first rigorous proof of the upper bound, showing that under the constraints \\(q<p<2q\\) and \\(e<\\varphi(N)\\), Wiener's attack applies for \\(d<\\frac 1 3 N^{\\frac 1 4}\\). In a new paper published in 2019, several researchers at the University of Wollongong in Australia further expanded the upper bound under the same constraints to \\[d\\leq \\frac 1 {\\sqrt[4]{18}} N^\\frac 1 4=\\frac 1 {2.06...}N^\\frac 1 4\\]

\n

Note that for simplicity, the above analysis of Wiener's attack mechanism is based on the Euler phi function \\(\\varphi (N)\\). In reality, RSA key pairs are often generated using the Carmichael function \\(\\lambda(N)\\). The relationship between the two is: \\[\\varphi (N)=\\lambda(n)\\cdot\\gcd(p-1,q-1)\\] It can be proven that starting from \\(ed≡1{\\pmod{\\lambda(N)}}\\), the same conclusions can be reached. Interested readers may refer to Wiener's original paper for details.

\n

Attack Workflow

\n

With an understanding of the mechanism of Wiener's attack, the attack workflow can be summarized as follows:

\n
    \n
  1. Expand \\(\\frac e N\\) into a continued fraction
  2. \n
  3. Generate the sequence of successive convergents of this continued fraction.
  4. \n
  5. Iteratively check each convergent's numerator \\(k\\) and denominator \\(d\\):\n
      \n
    • If \\(k\\) is zero, or \\(d\\) is even, or \\(ed\\not\\equiv 1 \\pmod k\\), skip this convergent.
    • \n
    • Calculate \\(\\varphi (N) = \\frac {ed-1} k\\), and solve for the integer roots p and q of the quadratic equation \\(x^2−(N−φ(N)+1)x+N\\).
    • \n
    • Verify if \\(N = p \\cdot q\\), if true, the attack succeeds and return \\((p, q, d)\\); otherwise continue.
    • \n
    • If all convergents are checked and no match, Wiener's attack fails.
    • \n
  6. \n
\n

The complete Python implementation is as follows:

\n
import gmpy2
import random

def solve_rsa_primes(s: int, m: int) -> tuple:
""" Solve RSA prime numbers (p, q) from the quadratic equation
p^2 - s * p + m = 0 with the formula p = s/2 +/- sqrt((s/2)^2 - m)
Parameters:
s - sum of primes (p + q)
m - product of primes (p * q)
Return: (p, q)
"""
half_s = s >> 1
tmp = gmpy2.isqrt(half_s ** 2 - m)
return int(half_s + tmp), int(half_s - tmp)

def wiener_attack(n: int, e: int) -> (int, int, int):
""" Wiener's Attack on RSA public key cryptosystem
Parameters:
N - RSA modulus N = p*q
e - RSA public exponent
Return:
A tuple of (p, q, d)
p, q - the two prime factors of RSA modulus N
d - RSA private exponent
"""
cfe = cf_expansion(e, n) # Convert e/n into a continued fraction
cvg = cf_convergent(cfe) # Get all of its convergents

for k, d in cvg:
# Check if k and d meet the requirements
if k == 0 or d % 2 == 0 or (e * d) % k != 1:
continue

# assume ed ≡ 1 (mod ϕ(n))
phi = (e * d - 1) // k
p, q = solve_rsa_primes(n - phi + 1, n)
if n == p * q:
return p, q, d

return None

def uint_to_bytes(x: int) -> bytes:
""" This works only for unsigned (non-negative) integers.
It does not work for 0."""
if x == 0:
return bytes(1)
return x.to_bytes((x.bit_length() + 7) // 8, 'big')

N = int(
'6727075990400738687345725133831068548505159909089226'\\
'9093081511054056173840933739311418333016536024767844'\\
'14065504536979164089581789354173719785815972324079')

e = int(
'4805054278857670490961232238450763248932257077920876'\\
'3637915365038611552743522891345050097418639182479215'\\
'15546177391127175463544741368225721957798416107743')

c = int(
'5928120944877154092488159606792758283490469364444892'\\
'1679423458017133739626176287570534122326362199676752'\\
'56510422984948872954949616521392542703915478027634')

p, q, d = wiener_attack(N, e)
assert not d is None, "Wiener's Attack failed!"
print("p =", p)
print("q =", q)
print("d =", d)
print(uint_to_bytes(pow(c, d, N)))

N = int(
'22836858353287668091920368816286415778103964252589'\\
'28295130420474999022996621982166664596581454018899'\\
'48429922376560732622754871538043874356270300826321'\\
'16650572564937978011181394388679265524940467869924'\\
'85473650038355720409426235584833584188449224331698'\\
'63569900296911605460645581176522325967221393273906'\\
'69673188457131381644120787783215342848744792830245'\\
'01805598140668893320307200136190794138325132168722'\\
'14217943474001731747822701596634040292342194986951'\\
'94551646668806852454006312372413658692027515557841'\\
'41440661232146905186431357112566536770669381756925'\\
'38179415478954522854711968599279014482060579354284'\\
'55238863726089083')

e = int(
'17160819308904585327789016134897914235762203050367'\\
'34632679585567058963995675965428034906637374660531'\\
'64750599687461192166424505919293706011293378320096'\\
'43372382766547546926535697752805239918767190684796'\\
'26509298669049485976118315666126871681847641670872'\\
'58895073919139366379901867664076540531765577090231'\\
'67209821832859747419658344363466584895316847817524'\\
'24703257392651850823517297420382138943770358904660'\\
'59442300191228592937251734592732623207324742303631'\\
'32436274414264865868028527840102483762414082363751'\\
'87208612632105886502393648156776330236987329249988'\\
'11429508256124902530957499338336903951924035916501'\\
'53661610070010419')

d = wiener_attack(N, e)
assert not d is None, "Wiener's attack failed!"
print("d =", d)

old_b = int(gmpy2.root(N, 4)/3)
new_b = int(gmpy2.root(N, 4)/gmpy2.root(18, 4))
print("old_b =", old_b)
print("new_b =", new_b)
assert d > old_b and d <= new_b
\n

The code above ends with two test cases. Referring to the program output below, the first test case gives a small RSA modulus \\(N\\) and a relatively large \\(e\\), which is precisely the scenario where Wiener's attack comes into play. The program calls the attack function wiener_attack() that quickly returns \\(d\\) as 7, then decrypts a ciphertext and recovers the original plaintext \"Wiener's attack success!\".

\n

The second test case sets a 2048-bit \\(N\\) and \\(e\\), and Wiener's attack also succeeds swiftly. The program also verifies that the cracked \\(d\\) (511 bits) is greater than the old bound old_b (\\(N^{\\frac 1 4}\\)), but slightly less than the new bound new_b (\\(\\frac 1 {\\sqrt[4]{18}} N^\\frac 1 4\\)). This confirms the conclusion of the University of Wollongong researchers.

\n
p = 105192975360365123391387526351896101933106732127903638948310435293844052701259
q = 63949859459297920725542167940404754256294386312715512490347273751054137071981
d = 7
b"Wiener's attack success!"
d = 5968166949079360555220268992852191823920023811474288738674370592596189517443887780023653031793516493806462114248181371416016184480421640973439863346079123
old_b = 4097678063688683751669784036917434915284399064709500941393388469932708726583832656910141469383433913840738001283204519671690533047637554279688711463501824
new_b = 5968166949079360962136673400587903792234115710617172051628964885379180548131448950677569697264501402772121272285767654845001503996650347315559383468867584
\n

These two test cases prove the effectiveness and prerequisites of Wiener's attack. To prevent Wiener's attack, the RSA private exponent \\(d\\) must be greater than the upper bound. Choosing \\(d\\) no less than \\(N^{\\frac 1 2}\\) is a more prudent scheme. In practice, the optimized decryption using Fermat's theorem and Chinese remainder theorem is often used, so that even larger \\(d\\) can achieve fast decryption and digital signing.

\n
\n

To be continued, stay tuned for the next article: RSA: Attack and Defense (III)

\n
\n
\n
\n
    \n
  1. John Pollard, a British mathematician, the recipient of 1999 RSA Award for Excellence in Mathematics for major contributions to algebraic cryptanalysis of integer factorization and discrete logarithm.↩︎

  2. \n
  3. Richard Peirce Brent, an Australian mathematician and computer scientist, an emeritus professor at the Australian National University.↩︎

  4. \n
  5. M. Wiener, “Cryptanalysis of short RSA secret exponents,” IEEE Trans. Inform. Theory, vol. 36, pp. 553–558, May 1990↩︎

  6. \n
  7. Adrien-Marie Legendre (1752-1833), a French mathematician who made numerous contributions to mathematics.↩︎

  8. \n
  9. Refer to Solve picoCTF's RSA Challenge Sum-O-Primes↩︎

  10. \n
  11. Dan Boneh, an Israeli–American professor in applied cryptography and computer security at Stanford University, a member of the National Academy of Engineering.↩︎

  12. \n
\n
\n","categories":["Technical Know-how"],"tags":["Cryptography","Network Security","Python Programming"]},{"title":"Implement Textbook RSA in Python","url":"/en/2022/01/22/Python-Textbook-RSA/","content":"

RSA encryption algorithm is one of the core technologies of modern public-key cryptography and is widely used on the Internet. As a classical algorithm of public-key cryptography, the programming implementation of textbook RSA can help us quickly grasp its mathematical mechanism and design ideas, and accumulate important experience in the software implementation of cryptography. Here is a detailed example of textbook RSA implementation in Python 3.8 programming environment.

\n

Random numbers should not be generated with a method chosen at random.
Donald Knuth(American computer scientist, mathematician, and professor emeritus at Stanford University, the 1974 recipient of the ACM Turing Award, often called the \"father of the analysis of algorithms\")

\n
\n

Generating Large Primes

\n

The security of the RSA encryption algorithm is built on the mathematical challenge of factoring the product of two large prime numbers. The first step in constructing the RSA encryption system is to generate two large prime numbers \\(p\\) and \\(q\\), and calculate the modulus \\(N=pq\\). \\(N\\) is the length of the RSA key, the larger the more secure. Nowadays, practical systems require the key length to be no less than 2048 bits, with corresponding \\(p\\) and \\(q\\) about 1024 bits each.

\n

A general effectiveness method for generating such large random prime numbers is a probability-based randomization algorithm, which proceeds as follows:

\n
    \n
  1. Pre-select random numbers of given bit length
  2. \n
  3. Do a primality test with small prime numbers (Sieve of Eratosthenes)\n
      \n
    • If it passes, continue to the third step
    • \n
    • If it fails, return to the first step
    • \n
  4. \n
  5. Perform advanced prime test (Miller-Rabin algorithm)\n
      \n
    • If it passes, output the presumed prime numbers
    • \n
    • If it fails, return to the first step
    • \n
  6. \n
\n

In this software implementation, the first step can generate odd numbers directly. Also for demonstration purposes, the second step uses the first 50 prime numbers greater than 2 for the basic primality test. The whole process is shown in the following flowchart.

\n

\n

For the first step, Python function programming requires importing the library function randrange() from the random library. The function uses the input number of bits n in the exponents of 2, which specify the start and end values of randrange(). It also sets the step size to 2 to ensure that only n-bit random odd values are returned.

\n
from random import randrange

def generate_n_bit_odd(n: int):
'''Generate a random odd number in the range [2**(n-1)+1, 2**n-1]'''
assert n > 1
return randrange(2 ** (n - 1) + 1, 2 ** n, 2)
\n

The code for the second step is simple. It defines an array with elements of 50 prime numbers after 2, then uses a double loop in the function to implement the basic primality test. The inner for loop runs the test with the elements of the prime array one by one. It aborts back to the outer loop immediately upon failure, from there it calls the function in the first step to generate the next candidate odd number and test again.

\n
def get_lowlevel_prime(n):
"""Generate a prime candidate not divisible by first primes"""
while True:
# Obtain a random odd number
c = generate_n_bit_odd(n)

# Test divisibility by pre-generated primes
for divisor in first_50_primes:
if c % divisor == 0 and divisor ** 2 <= c:
break
else:
# The for loop did not encounter a break statement,
# so it passes the low-level primality test.
return c
\n

The Miller-Rabin primality test1 in the third step is a widely used method for testing prime numbers. It uses a probabilistic algorithm to determine whether a given number is a composite or possibly a prime number. Although also based on Fermat's little theorem, the Miller-Rabin primality test is much more efficient than the Fermat primality test. Before showing the Python implementation of the Miller-Rabin prime test, a brief description of how it works is given here.

\n

By Fermat's little theorem, for a prime \\(n\\), if the integer \\(a\\) is not a multiple of \\(n\\), then we have \\(a^{n-1}\\equiv 1\\pmod n\\). Therefore if \\(n>2\\), \\(n-1\\) is an even number and must be expressed in the form \\(2^{s}*d\\), where both \\(s\\) and \\(d\\) are positive integers and \\(d\\) is odd. This yields \\[a^{2^{s}*d}\\equiv 1\\pmod n\\] If we keep taking the square root of the left side of the above equation and then modulo it, we will always get \\(1\\) or \\(-1\\)2. If we get \\(1\\), it means that the following equation ② holds; if we never get \\(1\\), then equation ① holds: \\[a^{d}\\equiv 1{\\pmod {n}}{\\text{ ①}}\\] \\[a^{2^{r}d}\\equiv -1{\\pmod {n}}{\\text{ ②}}\\] where \\(r\\) is some integer that lies in the interval \\([0, s-1]\\). So, if \\(n\\) is a prime number greater than \\(2\\), there must be either ① or ② that holds. The conditional statement of this law is also true, i.e.** if we can find a \\(\\pmb{a}\\) such that for any \\(\\pmb{0\\leq r\\leq s-1}\\) the following two equations are satisfied: \\[\\pmb{a^{d}\\not \\equiv 1\\pmod n}\\] \\[\\pmb{a^{2^{r}d}\\not \\equiv -1\\pmod n}\\] Then \\(\\pmb{n}\\) must not be a prime number**. This is the mathematical concept of the Miller-Rabin primality test. For the number \\(n\\) to be tested, after calculating the values of \\(s\\) and \\(d\\), the base \\(a\\) is chosen randomly and the above two equations are tested iteratively. If neither holds, \\(n\\) is a composite number, otherwise, \\(n\\) may be a prime number. Repeating this process, the probability of \\(n\\) being a true prime gets larger and larger. Calculations show that after \\(k\\) rounds of testing, the maximum error rate of the Miller-Rabin primality test does not exceed \\(4^{-k}\\).

\n

The Miller-Rabin primality test function implemented in Python is as follows, with the variables n,s,d,k in the code corresponding to the above description.

\n
def miller_rabin_primality_check(n, k=20):
'''Miller-Rabin Primality Test with a specified round of test
Input:
n - n > 3, an odd integer to be tested for primality
k - the number of rounds of testing to perform
Output:
True - passed (n is a strong probable prime)
False - failed (n is a composite)'''

# For a given odd integer n > 3, write n as (2^s)*d+1,
# where s and d are positive integers and d is odd.
assert n > 3
if n % 2 == 0:
return False

s, d = 0, n - 1
while d % 2 == 0:
d >>= 1
s += 1

for _ in range(k):
a = randrange(2, n - 1)
x = pow(a, d, n)

if x == 1 or x == n - 1:
continue

for _ in range(s):
x = pow(x, 2, n)
if x == n - 1:
break
else:
# The for loop did not encounter a break statement,
# so it fails the test, it must be a composite
return False

# Passed the test, it is a strong probable prime
return True
\n

Putting all of the above together, the whole process can be wrapped into the following function, where the input of the function is the number of bits and the output is a presumed random large prime number.

\n
def get_random_prime(num_bits):
while True:
pp = get_lowlevel_prime(num_bits)
if miller_rabin_primality_check(pp):
return pp
\n

Utility Functions

\n
    \n
  1. Greatest Common Divisor (GCD) gcd(a,b) and Least Common Multiple lcm(a,b):
    \nThe RSA encryption algorithm needs to calculate the Carmichael function \\(\\lambda(N)\\) of modulus \\(N\\), with the formula \\(\\lambda(pq)= \\operatorname{lcm}(p - 1, q - 1)\\), where the least common multiple function is used. The relationship between the least common multiple and the greatest common divisor is: \\[\\operatorname{lcm}(a,b)={\\frac{(a\\cdot b)}{\\gcd(a,b)}}\\] There is an efficient Euclidean algorithm for finding the greatest common divisor, which is based on the principle that the greatest common divisor of two integers is equal to the greatest common divisor of the smaller number and the remainder of the division of the two numbers. The specific implementation of Euclid's algorithm can be done iteratively or recursively. The iterative implementation of the maximum convention function is applied here, and the Python code for the two functions is as follows:

    \n

    def gcd(a, b):
    '''Computes the Great Common Divisor using the Euclid's algorithm'''
    while b:
    a, b = b, a % b
    return a

    def lcm(a, b):
    """Computes the Lowest Common Multiple using the GCD method."""
    return a // gcd(a, b) * b

  2. \n
  3. Extended Euclidean Algorithm exgcd(a,b) and Modular Multiplicative Inverse invmod(e,m):
    \nThe RSA key pair satisfies the equation \\((d⋅e)\\bmod \\lambda(N)=1\\), i.e., the two are mutually modular multiplicative inverses with respect to the modulus \\(\\lambda(N)\\). The extended Euclidean algorithm can be applied to solve the modular multiplicative inverse \\(d\\) of the public key exponent \\(e\\) quickly. The principle of the algorithm is that given integers \\(a,b\\), it is possible to find integers \\(x,y\\) (one of which is likely to be negative) while finding the greatest common divisor of \\(a,b\\) such that they satisfy Bézout's identity: \\[a⋅x+b⋅y=\\gcd(a, b)\\] substituted into the parameters \\(a=e\\) and \\(b=m=\\lambda(N)\\) of the RSA encryption algorithm, and since \\(e\\) and \\(\\lambda(N)\\) are coprime, we can get: \\[e⋅x+m⋅y=1\\] the solved \\(x\\) is the modulo multiplicative inverse \\(d\\) of \\(e\\). The Python implementations of these two functions are given below:

    \n

    def exgcd(a, b):
    """Extended Euclidean Algorithm that can give back all gcd, s, t
    such that they can make Bézout's identity: gcd(a,b) = a*s + b*t
    Return: (gcd, s, t) as tuple"""
    old_s, s = 1, 0
    old_t, t = 0, 1
    while b:
    q = a // b
    s, old_s = old_s - q * s, s
    t, old_t = old_t - q * t, t
    a, b = b, a % b
    return a, old_s, old_t

    def invmod(e, m):
    """Find out the modular multiplicative inverse x of the input integer
    e with respect to the modulus m. Return the minimum positive x"""
    g, x, y = exgcd(e, m)
    assert g == 1

    # Now we have e*x + m*y = g = 1, so e*x ≡ 1 (mod m).
    # The modular multiplicative inverse of e is x.
    if x < 0:
    x += m
    return x
    Similarly, an iterative approach is applied here to implement the extended Euclidean algorithm, with the modular inverse multiplicative function calling the former.

  4. \n
\n

Implementing RSA Class

\n

Note: Textbook RSA has inherent security vulnerabilities. The reference implementation in the Python language given here is for learning and demonstration purposes only, by no means to be used in actual applications. Otherwise, it may cause serious information security incidents. Keep this in mind!

\n
\n

Based on the object-oriented programming idea, it can be designed to encapsulate the RSA keys and all corresponding operations into a Python class. The decryption and signature generation of the RSA class are each implemented in two ways, regular and fast. The fast method is based on the Chinese Remainder Theorem and Fermat's Little Theorem. The following describes the implementation details of the RSA class.

\n
    \n
  1. Object Initialization Method
    \nInitialization method __init__() has the user-defined paramaters with default values shown as below:

    \n
      \n
    • Key bit-length (\\(N\\)):2048
    • \n
    • Public exponent (\\(e\\)):65537
    • \n
    • Fast decryption or signature generation:False
    • \n
    \n

    This method internally calls the get_random_prime() function to generate two large random prime numbers \\(p\\) and \\(q\\) that are about half the bit-length of the key. It then calculates their Carmichael function and verifies that the result and \\(e\\) are coprime. If not, it repeats the process till found. Thereafter it computes the modulus \\(N\\) and uses the modular multiplicative inverse function invmod() to determine the private exponent \\(d\\). If a fast decryption or signature generation function is required, three additional values are computed as follows: \\[\\begin{align}\nd_P&=d\\bmod (p-1)\\\\\nd_Q&=d\\bmod (q-1)\\\\\nq_{\\text{inv}}&=q^{-1}\\pmod {p}\n\\end{align}\\]

    \n

    RSA_DEFAULT_EXPONENT = 65537
    RSA_DEFAULT_MODULUS_LEN = 2048

    class RSA:
    """Implements the RSA public key encryption/decryption with default
    exponent 65537 and default key size 2048"""

    def __init__(self, key_length=RSA_DEFAULT_MODULUS_LEN,
    exponent=RSA_DEFAULT_EXPONENT, fast_decrypt=False):
    self.e = exponent
    self.fast = fast_decrypt
    t = 0
    p = q = 2

    while gcd(self.e, t) != 1:
    p = get_random_prime(key_length // 2)
    q = get_random_prime(key_length // 2)
    t = lcm(p - 1, q - 1)

    self.n = p * q
    self.d = invmod(self.e, t)

    if (fast_decrypt):
    self.p, self.q = p, q
    self.d_P = self.d % (p - 1)
    self.d_Q = self.d % (q - 1)
    self.q_Inv = invmod(q, p)

  2. \n
  3. Encryption and Decryption Methods
    \nRSA encryption and regular decryption formulas are \\[\\begin{align}\nc\\equiv m^e\\pmod N\\\\\nm\\equiv c^d\\pmod N\n\\end{align}\\] Python built-in pow() function supports modular exponentiation. The above two can be achieved by simply doing the corresponding integer to byte sequence conversions and then calling pow() with the public or private key exponent:

    \n

    def encrypt(self, binary_data: bytes):
    int_data = uint_from_bytes(binary_data)
    return pow(int_data, self.e, self.n)
    \t
    def decrypt(self, encrypted_int_data: int):
    int_data = pow(encrypted_int_data, self.d, self.n)
    return uint_to_bytes(int_data)
    For fast descryption, a few extra steps are needed: \\[\\begin{align}\nm_1&=c^{d_P}\\pmod {p}\\tag{1}\\label{eq1}\\\\\nm_2&=c^{d_Q}\\pmod {q}\\tag{2}\\label{eq2}\\\\\nh&=q_{\\text{inv}}(m_1-m_2)\\pmod {p}\\tag{3}\\label{eq3}\\\\\nm&=m_{2}+hq\\pmod {pq}\\tag{4}\\label{eq4}\n\\end{align}\\] In practice, if \\(m_1-m_2<0\\) in the step \\((3)\\), \\(p\\) needs to be added to adjust to a positive number. It can also be seen that the acceleration ratio would theoretically be close to \\(4\\) because the fast decryption method decreases the modulus and exponent by roughly half the order. Considering the additional computational steps, the actual speedup ratio estimate is subtracted by a correction \\(\\varepsilon\\), noted as \\(4-\\varepsilon\\). The code of the fast decryption function is as follows:

    \n

    def decrypt_fast(self, encrypted_int_data: int):
    # Use Chinese Remaider Theorem + Fermat's Little Theorem to
    # do fast RSA description
    assert self.fast == True
    m1 = pow(encrypted_int_data, self.d_P, self.p)
    m2 = pow(encrypted_int_data, self.d_Q, self.q)
    t = m1 - m2
    if t < 0:
    t += self.p
    h = (self.q_Inv * t) % self.p
    m = (m2 + h * self.q) % self.n
    return uint_to_bytes(m)

  4. \n
  5. Signature Generation and Verification Methods
    \nThe RSA digital signature generation and verification methods are very similar to encryption and regular decryption functions, except that the public and private exponents are used in reverse. The signature generation uses the private exponent, while the verification method uses the public key exponent. The implementation of fast signature generation is the same as the fast decryption steps, but the input and output data are converted and adjusted accordingly. The specific implementations are presented below:

    \n

    def generate_signature(self, encoded_msg_digest: bytes):
    """Use RSA private key to generate Digital Signature for given
    encoded message digest"""
    int_data = uint_from_bytes(encoded_msg_digest)
    return pow(int_data, self.d, self.n)
    \t
    def generate_signature_fast(self, encoded_msg_digest: bytes):
    # Use Chinese Remaider Theorem + Fermat's Little Theorem to
    # do fast RSA signature generation
    assert self.fast == True
    int_data = uint_from_bytes(encoded_msg_digest)
    s1 = pow(int_data, self.d_P, self.p)
    s2 = pow(int_data, self.d_Q, self.q)
    t = s1 - s2
    if t < 0:
    t += self.p
    h = (self.q_Inv * t) % self.p
    s = (s2 + h * self.q) % self.n
    return s

    def verify_signature(self, digital_signature: int):
    """Use RSA public key to decrypt given Digital Signature"""
    int_data = pow(digital_signature, self.e, self.n)
    return uint_to_bytes(int_data)

  6. \n
\n

Functional Tests

\n

Once the RSA class is completed, it is ready for testing. To test the basic encryption and decryption functions, first initialize an RSA object with the following parameters

\n
    \n
  • Key length (modulo \\(N\\)): 512 bits
  • \n
  • Public exponent (\\(e\\)): 3
  • \n
  • Fast decryption or signature generation: True
  • \n
\n

Next, we can call the encryption method encrypt() of the RSA object instance to encrypt the input message, and then feed the ciphertext to the decryption method decrypt() and the fast decryption method decrypt_fast() respectively. We use the assert statement to compare the result with the original message. The code snippet is as follows.

\n
# ---- Test RSA class ----
alice = RSA(512, 3, True)
msg = b'Textbook RSA in Python'
ctxt = alice.encrypt(msg)
assert alice.decrypt(ctxt) == msg
assert alice.decrypt_fast(ctxt) == msg
print("RSA message encryption/decryption test passes!")
\n

Likewise, we can also test the signature methods. In this case, we need to add the following import statement to the beginning of the file

\n
from hashlib import sha1
\n

This allows us to generate the message digest with the library function sha1() and then call the generate_signature() and generate_signature_fast() methods of the RSA object instance to generate the signature, respectively. Both signatures are fed to the verify_signature()` function and the result should be consistent with the original message digest. This test code is shown below.

\n
mdg = sha1(msg).digest()
sign1 = alice.generate_signature(mdg)
sign2 = alice.generate_signature_fast(mdg)

assert alice.verify_signature(sign1) == mdg
assert alice.verify_signature(sign2) == mdg
print("RSA signature generation/verification test passes!")
\n

If no AssertionError is seen, we would get the following output, indicating that both the encryption and signature tests passed.

\n
RSA message encryption/decryption test passes!
RSA signature generation/verification test passes!
\n

Performance Tests

\n

Once the functional tests are passed, it is time to see how the performance of fast decryption is. We are interested in what speedup ratio we can achieve, which requires timing the execution of the code. For time measurements in Python programming, we have to import the functions urandom() and timeit() from the Python built-in libraries os and timeit, respectively:

\n
from os import urandom
from timeit import timeit
\n

urandom() is for generaring random bype sequence, while timeit() can time the execution of a given code segment. For the sake of convenience, the RSA decryption methods to be timed are first packed into two functions:

\n
    \n
  • decrypt_norm() - Regular decryption method
  • \n
  • decrypt_fast() - Fast descryption method
  • \n
\n

Both use the assert statement to check the result, as shown in the code below:

\n
def decrypt_norm(tester, ctxt: bytes, msg: bytes):
ptxt = tester.decrypt(ctxt)
assert ptxt == msg

def decrypt_fast(tester, ctxt: bytes, msg: bytes):
ptxt = tester.decrypt_fast(ctxt)
assert ptxt == msg
\n

The time code sets up two nested for loops:

\n
    \n
  • The outer loop iterates over different key lengths klen, from 512 bits to 4096 bits in 5 levels, and the corresponding RSA object obj is initialized with:

    \n
      \n
    • Key length (modular \\(N\\)): klen
    • \n
    • Public exponent (\\(e\\)): 65537
    • \n
    • Fast decryption or signature generation: True
    • \n
    \n

    The variable rpt is also set in the outer loop to be the square root of the key length, and the timing variables t_n and t_f are cleared to zeros.

  • \n
  • The inner layer also loops 5 times, each time executing the following operations:

    \n
      \n
    • Call urandom() to generate a random sequence of bytes mg with bits half the length of the key
    • \n
    • Call obj.encrypt() to generate the ciphertext ct
    • \n
    • call timeit() and enter the packing functions decrypt_norm() and decrypt_fast() with the decryption-related parameters obj, ct and mg, respectively, and set the number of executions to rpt
    • \n
    • The return values of the timeit() function are stored cumulatively in t_n and t_f
    • \n
  • \n
\n

At the end of each inner loop, the current key length, the mean value of the timing statistics, and the calculated speedup ratio t_n/t_f are printed. The actual program segment is printed below:

\n
print("Start RSA fast decryption profiling...")
for klen in [512, 1024, 2048, 3072, 4096]:
rpt = int(klen ** 0.5)
obj = RSA(klen, 65537, True)
t_n = t_f = 0
for _ in range(5):
mg = urandom(int(klen/16))
ct = obj.encrypt(mg)
t_n += timeit(lambda: decrypt_norm(obj, ct, mg), number=rpt)
t_f += timeit(lambda: decrypt_fast(obj, ct, mg), number=rpt)
print("Key size %4d => norm %.4fs, fast %.4fs\\tSpeedup: %.2f"
% (klen, t_n/5/rpt, t_f/5/rpt, t_n/t_f))
\n

Here are the results on a Macbook Pro laptop:

\n
Start RSA fast decryption profiling...
Key size 512 => norm 0.0008s, fast 0.0003s Speedup: 2.43
Key size 1024 => norm 0.0043s, fast 0.0015s Speedup: 2.88
Key size 2048 => norm 0.0273s, fast 0.0085s Speedup: 3.19
Key size 3072 => norm 0.0835s, fast 0.0240s Speedup: 3.48
Key size 4096 => norm 0.1919s, fast 0.0543s Speedup: 3.53
\n

The test results confirm the effectiveness of the fast decryption method. As the key length increases, the computational intensity gradually increases and the running timeshare of the core decryption operation becomes more prominent, so the speedup ratio grows correspondingly. However, the final speedup ratio tends to a stable value of about 3.5, which is consistent with the upper bound of the theoretical estimate (\\(4-\\varepsilon\\)).

\n

The Python code implementation of the textbook RSA helps reinforce the basic number theory knowledge we have learned and also benefits us with an in-depth understanding of the RSA encryption algorithm. On this basis, we can also extend to experiment some RSA elementary attack and defense techniques to further master this key technology of public-key cryptography. For the complete program click here to download: textbook-rsa.py.gz

\n
\n
\n
    \n
  1. Gary Lee Miller, a professor of computer science at Carnegie Mellon University, first proposed a deterministic algorithm based on the unproven generalized Riemann hypothesis. Later Professor Michael O. Rabin of the Hebrew University of Jerusalem, Israel, modified it to obtain an unconditional probabilistic algorithm.↩︎

  2. \n
  3. This is because it follows from \\(x^2\\equiv 1\\pmod n\\) that \\((x-1)(x+1)=x^{2}-1\\equiv 0\\pmod n\\). Since \\(n\\) is a prime number, by Euclid's Lemma, it must divide either \\(x- 1\\) or \\(x+1\\), so \\(x\\bmod n\\) must be \\(1\\) or \\(-1\\).↩︎

  4. \n
\n
\n","categories":["Technical Know-how"],"tags":["Cryptography","Python Programming"]},{"title":"RSA: Attack and Defense (I)","url":"/en/2023/03/16/RSA-attack-defense/","content":"

RSA is a public-key cryptosystem built on top of an asymmetric encryption algorithm, which was jointly invented by three cryptographers and computer scientists at the Massachusetts Institute of Technology in 1977. The RSA public-key encryption algorithm and cryptosystem provide data confidentiality and signature verification functions widely used on the Internet. Since its birth, RSA has become a major research object of modern cryptography. Many cryptanalysts and information security experts have been studying its possible theoretical flaws and technical loopholes to ensure security and reliability in practical applications.

\n

There are certain things whose number is unknown. If we count them by threes, we have two left over; by fives, we have three left over; and by sevens, two are left over. How many things are there?
Sunzi Suanjing, Volume 2.26

\n
\n

Fortunately, after more than 40 years of extensive research and practical application tests, although many sophisticated attack methods have been discovered, RSA is generally safe. These attack methods all take advantage of the improper use of RSA or the vulnerability of software and hardware implementations, and cannot shake the security foundation of its encryption algorithm. On the other hand, the research on these attack methods shows that implementing a safe and robust RSA application is not a simple task. A consensus in cryptography and network security hardware and software engineering practice is: never roll your own cryptography!1 The appropriate solution is to use an existing, well-tested, and reliably maintained library or API to implement the RSA algorithm and protocol application.

\n

Here is a brief survey of the common means of attacking RSA, the mathematical mechanism on which the attack is based, and the corresponding protective measures. Referring to the previous article, let’s start by reviewing the working mechanism and process of RSA:

\n
    \n
  1. Choose two large prime numbers \\(p\\) and \\(q\\), compute \\(N=pq\\)
  2. \n
  3. Compute \\(\\lambda(N)\\), where \\(\\lambda\\) is Carmichael's totient function\n
      \n
    • When both \\(p\\) and \\(q\\) are prime, \\(\\lambda(pq)=\\operatorname {lcm}(p − 1, q − 1)\\)
    • \n
    • \\(\\operatorname{lcm}\\) is a function to find the least common multiple, which can be calculated by the Euclidean algorithm
    • \n
  4. \n
  5. Choose a number \\(e\\) that is less than \\(\\lambda(N)\\) and also coprime with it, then calculate the modular multiplicative inverse of \\(e\\) modulo \\(\\lambda(N)\\). That is \\(d\\equiv e^{-1}\\pmod {\\lambda(N)}\\)\n
      \n
    • Per the definition of modular multiplicative inverse, find \\(d\\) such that \\((d⋅e)\\bmod\\lambda(N)=1\\)
    • \n
    • A modular multiplicative inverse can be found by using the extended Euclidean algorithm
    • \n
  6. \n
  7. \\(\\pmb{(N,e)}\\) is the public key\\(\\pmb{(N,d)}\\) is the private key\n
      \n
    • The public key can be known by everyone, but the private key must be kept secret
    • \n
    • The records of \\(p,q,\\lambda(N)\\) can all be discarded
    • \n
  8. \n
  9. The sender first converts the message into a positive integer less than \\(N\\) according to the agreed encoding format, then uses the receiver's public key to compute the ciphertext with the formula \\(\\pmb{c\\equiv m^e\\pmod N}\\)
  10. \n
  11. After receiving the ciphertext, the receiver uses its private key to compute the plaintext \\(m\\) with the formula \\(\\pmb{m\\equiv c^d\\pmod N}\\), then decodes it into the original message
  12. \n
  13. A message encrypted with the private key can also be decrypted by the public key, i.e. if \\(\\pmb{s\\equiv m^d\\pmod N}\\), \\(\\pmb{m\\equiv s^e\\pmod N}\\). This is the supported digital signature feature
  14. \n
\n

Note that the second and third steps in the original RSA paper use Euler's totient function \\(\\varphi(N)\\) instead of \\(\\lambda(N)\\). The relationship between these two functions is: \\[\\varphi(N)=\\lambda(N)⋅\\operatorname{gcd}(p-1,q-1)\\] Here \\(\\operatorname{gcd}\\) is the greatest common divisor function. Using \\(\\lambda(N)\\) can yield the minimum workable private exponent \\(d\\), which is conducive to efficient decryption and signature operations. Implementations that follow the above procedure, whether using Euler's or Carmichael's functions, are often referred to as \"textbook RSA \".

\n

Textbook RSA is insecure, and there are many simple and effective means of attack. Before discussing the security holes of the textbook RSA in detail, it is necessary to review the first known attack method - integer factorization!

\n

Integer Factorization

\n

The theoretical cornerstone of the security of the RSA encryption algorithm is the problem of factoring large numbers. If we can separate \\(p\\) and \\(q\\) from the known \\(N\\), we can immediately derive the private exponent \\(d\\) and thus completely crack RSA. Factoring large numbers is a presumed difficult computational problem. The best-known asymptotic running time algorithm is General Number Field Sieve, and its time complexity is \\({\\displaystyle \\exp \\left(\\left(c+o(1)\\right)(\\ln N)^{\\frac {1}{3}}(\\ln \\ln N)^{\\frac {2}{3}}\\right)}\\), where the constant \\(c = 4/\\sqrt[3]{9}\\)\\(\\displaystyle \\exp\\) and \\(\\displaystyle \\exp\\) is the exponential function of Euler's number (2.718).

\n

For a given large number, it is difficult to accurately estimate the actual complexity of applying the GNFS algorithm. However, based on the heuristic complexity empirical estimation, we can roughly see the increasing trend of computational time complexity:

\n
    \n
  • For a large number of 1024 bits, there are two prime factors of about 500 bits each, and the decomposition requires basic arithmetic operations of order \\(2^{70}\\)
  • \n
  • For a large number of 2048 bits, there are two prime factors of about 1000 bits each, and the decomposition requires basic arithmetic operations of order \\(2^{90}\\), a million times slower than the 1024-bit number
  • \n
\n

The rapid development of computer software and hardware technology has made many tasks that seemed impossible in the past become a reality. Check the latest record released by the RSA Factoring Challenge website. In February 2020, a team led by French computational mathematician Paul Zimmermann successfully decomposed the large number RSA-250 with 250 decimal digits (829 bits):

\n
RSA-250 = 6413528947707158027879019017057738908482501474294344720811685963202453234463
0238623598752668347708737661925585694639798853367
× 3337202759497815655622601060535511422794076034476755466678452098702384172921
0037080257448673296881877565718986258036932062711
\n

announcement

\n

According to the announcement of the factorization released by Zimmerman, using a 2.1GHz Intel Xeon Gold 6130 processor, the total computing time to complete this task is about 2700 CPU core-years. This number may seem large, but in today's era of cluster computing, grid computing, and cloud computing for the masses, it's not a stretch to think that organizations with strong financial backing can reduce computing time to hours or even minutes. As an example, go to the online tool website of the free open-source mathematical software system SageMath and enter the following first 5 lines of Sage Python code:

\n
p=random_prime(2**120)
q=random_prime(2**120)
n=p*q
print(n)
factor(n)
# The output
28912520751034191277571809785701738245635791077300278534278526509273423
38293227899687810929829874029597363 * 755029605411506802434801930237797621
\n

The result was obtained within minutes, and a large number of 72 decimal digits (240 bits) was decomposed. You know, in the 1977 RSA paper, it is mentioned that it takes about 104 days to decompose a 75-digit decimal number. The technological progress of mankind is so amazing!

\n

As the attacker's spear becomes sharper and sharper, the defender's shield must become thicker and thicker. Therefore, 1024-bit RSA is no longer secure, and applications should not use public key \\(N\\) values that are less than 2048 bits. And when high security is required, choose 4096-bit RSA.

\n

Elementary Attacks

\n

Although the decomposition of large numbers is an attack method known to everyone, the security vulnerabilities caused by some low-level errors commonly found in RSA applications make it possible to use simple attacks to succeed, and some typical ones are explained below.

\n
    \n
  • In the early development of RSA, finding large prime numbers took quite a bit of time based on the backward computing power of the time. Therefore, some system implementations tried to share the modulus \\(N\\). The idea was to generate only one set \\((p,q)\\), and then all users would use the same \\(N=pq\\) values, with a central authority that everyone trusted assigning key pairs \\((e_i,d_i)\\) to each user \\(i\\), and nothing would go wrong as long as the respective private keys \\(d_i\\) were kept. Unfortunately, this is a catastrophic mistake! This implementation has two huge security holes:

    \n
      \n
    1. The user \\(i\\) can decompose \\(N\\) using his own key pair \\((e_i,d_i)\\). Whether \\(d\\) is generated using the Euler function \\(\\varphi(N)\\) or the Carmichael function \\(\\lambda(N)\\), there are algorithms that quickly derive the prime factors \\(p\\) and \\(q\\) from a given \\(d\\) 2. And once \\(p\\) and \\(q\\) are known, user \\(i\\) can compute any other user's private key \\(d_j\\) with one's public key \\((N,e_j)\\). At this point, the other users have no secrets from user \\(i\\).

    2. \n
    3. Even if all users do not have the knowledge and skill to decompose \\(N\\), or are \"nice\" enough not to know the other users' private keys, a hacker can still perform a common modulus attack to break the users' messages. If the public keys of two users, Alice and Bob, are \\(e_1\\) and \\(e_2\\), and \\(e_1\\) and \\(e_2\\) happen to be mutually prime (which is very likely), then by Bézout's identity, the eavesdropper Eve can find that \\(s\\) and \\(t\\) satisfy: \\[e_{1}s+e_{2}t=gcd(e_1,e_2)=1\\] At this point, if someone sends the same message \\(m\\) to Alice and Bob, Eve can decrypt \\(m\\) after recording the two ciphertexts \\(c_1\\) and \\(c_2\\) and performing the following operation: \\[c_1^s⋅c_2^t\\equiv(m^{e _1})^s⋅(m^{e_2})^t\\equiv m^{e_{1}s+e_{2}t}\\equiv m\\pmod N\\] The corresponding Python function code is shown below.

      \n

      def common_modulus(e1, e2, N, c1, c2):
      # Call the extended Euclidean algorithm function
      g, s, t = gymp2.gcdext(e1, e2)
      assert g == 1
      if s < 0:
      # Find c1's modular multiplicative inverse\t\t re = int(gmpy2.invert(c1, N))
      c1 = pow(re, s*(-1), N)
      c2 = pow(c2, t, N)
      else:
      # t is negative, find c2's modular multiplicative inverse
      re = int(gmpy2.invert(c2, N))
      c2 = pow(re, t*(-1), N)
      c1 = pow(c1, a, N)
      return (c1*c2) % N
      Two library functions of gmpy23 are called here: gcdext() to implement the extended Euclidean algorithm, and invert() to find the modular multiplicative inverse element. Note that Python's exponential function pow() supports modular exponentiation, but the exponent must not be negative. Since one of \\(s\\) or \\(t\\) must be negative, you have to first call invert() to convert \\(c_1\\) or \\(c_2\\) to the corresponding modular multiplicative inverse, then invert the negative number to calculate the modular exponent. For example, lines 7 and 8 above implement \\(c_1^s=(c_1^{-1})^{-s}\\bmod N\\).

    4. \n
  • \n
  • Is it possible to reuse only \\(p\\) or \\(q\\) since the shared modulus \\(N\\) is proven to be insecure? This seems to avoid the common-modulus attack and ensure that each user's public key \\(N\\) value is unique. Big mistake! This is an even worse idea! The attacker gets the public \\(N\\) values of all users and simply combines \\((N_1,N_2)\\) pairwise to solve Euclid's algorithm for the great common divisor, and a successful solution gives a prime factor \\(p\\), and a simple division gives the other prime factor \\(q\\). With \\(p\\) and \\(q\\), the attacker can immediately compute the user's private key \\(d\\). This is the non-coprime modulus attack.

  • \n
  • When applying textbook RSA, if both the public exponent \\(e\\) and the plaintext \\(m\\) are small, such that \\(c=m^e<N\\), the plaintext \\(m\\) can be obtained by directly calculating the \\(e\\)th root of the ciphertext \\(c\\). Even if \\(m^e>N\\) but not large enough, then since \\(m^e=c+k⋅N\\), you can loop through the small \\(k\\) values to perform brute-force root extraction cracking. Here is the Python routine:

    \n

    def crack_small(c, e, N, repeat)
    times = 0
    msg = 0
    for k in range(repeat):
    m, is_exact = gmpy2.iroot(c + times, e)
    if is_exact and pow(m, e, N) == c:
    msg = int(m)
    break
    times += N
    return msg
    Here the gmpy2 library function iroot() is called to find the \\(e\\)th root.

  • \n
  • Textbook RSA is deterministic, meaning that the same plaintext \\(m\\) always generates the same ciphertext \\(c\\). This makes codebook attack possible: the attacker precomputes all or part of the \\(m\\to c\\) mapping table and saves, then simply searches the intercepted ciphertext for a match. Determinism also means that textbook RSA is not semantically secure and that the ciphertext can reveal some information about the plaintext. Repeated occurrences of the ciphertext indicate that the sender is sending the same message over and over again.

  • \n
  • Textbook RSA is malleable, where a particular form of algebraic operation is performed on the ciphertext and the result is reflected in the decrypted plaintext. For example, if there are two plaintexts \\(m_1\\) and \\(m_2\\), and encryption yields \\(c_1=m_1^e\\bmod N\\) and \\(c_2=m_2^e\\bmod N\\), what does \\((c_1⋅c_2)\\) decryption yield? Look at the following equation: \\[(c_1⋅c_2)^d\\equiv m_1^{ed}⋅m_2^{ed}\\equiv m_1⋅m_2\\pmod N\\] So the plaintext obtained after decrypting the product of the two ciphertexts is equal to the product of the two plaintexts. This feature is detrimental to RSA encryption systems in general and provides an opportunity for chosen-ciphertext attack. The following are two examples of attack scenarios:

    \n
      \n
    1. Imagine that there is an RSA decryption machine that can decrypt messages with an internally saved private key \\((N,d)\\). For security reasons, the decryptor will reject repeated input of the same ciphertext. An attacker, Marvin, finds a piece of ciphertext \\(c\\) that is rejected by the decryptor when he enters it directly because the ciphertext \\(c\\) has been decrypted before. Marvin finds a way to crack it. He prepares a plaintext \\(r\\) himself, encrypts it with the public key \\((N,e)\\) to generate a new ciphertext \\(c'={r^e}c\\bmod N\\), and then feeds the ciphertext \\(c'\\) to the decryptor. The decryption machine has not decrypted this new ciphertext, so it will not reject it. The result of the decryption is \\[m'\\equiv (c')^d\\equiv r^{ed}c^d\\equiv rm\\pmod N\\] Now that Marvin has \\(m'\\), he can calculate \\(m\\) using the formula \\(m\\equiv m'r^{-1}\\pmod N\\).

    2. \n
    3. Suppose Marvin wants Bob to sign a message \\(m\\), but Bob refuses to do so after reading the message content. Marvin can achieve his goal by using an attack called blinding4. He picks a random message \\(r\\), generates \\(m'={r^e}m\\bmod N\\), and then takes \\(m'\\) to Bob to sign. Bob probably thinks \\(m'\\) is irrelevant and signs it. The result of Bob's signature is \\(s'=(m')^d\\bmod N\\). Now Marvin has Bob's signature on the original message \\(m\\) using the formula \\(s=s'r^{-1}\\bmod N\\). Why? The reason is that \\[s^e\\equiv (s')^er^{-e}\\equiv (m')^{ed}r^{-e}\\equiv m'r^{-e}\\equiv m\\pmod N\\]

    4. \n
  • \n
\n

The above is by no means a complete list of elementary attack methods, but they are illustrative. In practical RSA applications, we must be very careful and should do the following:

\n
    \n
  • generate a unique public key modulus \\(N\\) for each user individually to prevent common-mode attacks
  • \n
  • not reuse the prime factor to generate the public key modulus \\(N\\), to eliminate the non-coprime modulus attack
  • \n
\n

For the textbook RSA deterministic and malleable flaws, and possible brute-force root extraction cracking vulnerabilities, the padding with random elements method can be used to protect against them, and the protection is valid due to the following:

\n
    \n
  • Padding ensures that the number of bits in the encrypted message is close to \\(N\\), while not using small \\(e\\) values, making possible brute-force root extraction cracking ineffective
  • \n
  • Random padding makes the same plaintext produce different ciphertexts, guaranteeing semantic security and making ciphertext attacks impossible
  • \n
  • Strictly format-defined padding destroys malleability and reduces the possibility of ciphertext selection attacks. For example, if the first few bytes after padding must be a given value, the decrypted data will most likely not conform to the predefined format after the algebraic operation on the corresponding ciphertext, which disrupts the ciphertext selection attack.
  • \n
\n

Low Public Exponent Attacks

\n

Using low public exponent is dangerous, and there are advanced attacks in the case of non-padding or improper padding, even if brute-force root extraction cracking does not succeed.

\n

Broadcast Attack

\n

Discovered by Swedish theoretical computer scientist Johan Håstad 5, hence the name Håstad's Broadcast Attack. Consider this simplified scenario, assuming that Alice needs to send the same message \\(m\\) to Bob, Carol, and Dave. The public keys of the three recipients are \\((N_1,3)\\), \\((N_2,3)\\), and \\((N_3,3)\\), i.e., the public exponent is all 3 and the public key modulus is different for each. The messages are not padded and Alice directly encrypts and sends three ciphertexts \\(c_1,c_2,c_3\\) using the public keys of the other three:

\n

\\[\\begin{cases}\nc_1=m^3\\bmod N_1\\\\\nc_2=m^3\\bmod N_2\\\\\nc_3=m^3\\bmod N_3\n\\end{cases}\\]

\n

At this point Eve secretly writes down the three ciphertexts, marking \\(M=m^3\\), and if she can recover \\(M\\), running a cube root naturally yields the plaintext \\(m\\). Obviously, the common modulus attack does not hold here, and we can also assume that the moduli are pairwise coprime, or else decomposing the modulus using the non-coprime modulus attack will work. So does Eve have a way to compute \\(M\\)? The answer is yes.

\n

In fact, the equivalent problem for solving \\(M\\) here is: Is there an efficient algorithm for solving a number that has known remainders of the Euclidean division by several integers, under the condition that the divisors are pairwise coprime? This efficient algorithm is Chinese Remainder Theorem!

\n

The Chinese remainder theorem gives the criterion that a system of one-element linear congruence equations has a solution and the method to solve it. For the following system of one-element linear congruence equations (be careful not to confuse it with the mathematical notation used to describe the attack scenario above):

\n

\\[(S) : \\quad \\left\\{ \n\\begin{matrix} x \\equiv a_1 \\pmod {m_1} \\\\\nx \\equiv a_2 \\pmod {m_2} \\\\\n\\vdots \\qquad\\qquad\\qquad \\\\\nx \\equiv a_n \\pmod {m_n} \\end\n{matrix} \\right.\\]

\n

Suppose that the integers \\(m_1,m_2,\\ldots,m_n\\) are pairwise coprime, then the system of equations \\((S)\\) has a solution for any integer \\(a_1,a_2,\\ldots,a_n\\) and the general solution can be constructed in four steps as follows:

\n

\\[\\begin{align}\nM &= m_1 \\times m_2 \\times \\cdots \\times m_n = \\prod_{i=1}^n m_i \\tag{1}\\label{eq1}\\\\\nM_i &= M/m_i, \\; \\; \\forall i \\in \\{1, 2, \\cdots , n\\}\\tag{2}\\label{eq2}\\\\\nt_i M_i &\\equiv 1\\pmod {m_i}, \\; \\; \\forall i \\in \\{1, 2, \\cdots , n\\}\\tag{3}\\label{eq3}\\\\\nx &=kM+\\sum_{i=1}^n a_i t_i M_i\\tag{4}\\label{eq4}\n\\end{align}\\]

\n

The last line above, Eq. (4) gives the formula of the general solution. In the sense of modulus \\(M\\), the unique solution is \\(\\sum_{i=1}^n a_i t_i M_i \\bmod M\\).

\n

Try to solve the things whose number is unknown problem at the beginning of this article by using the Chinese remainder theorem

\n
\n

First, correspond the variable symbols to the values: \\[m_1=3,a_1=2;\\quad m_2=5,a_2=3;\\quad m_3=7,a_3=2\\] Then calculate \\(M=3\\times5\\times7=105\\), which in turn leads to the derivation of: \\[\\begin{align}\nM_1 &=M/m_1=105/3=35,\\quad t_1=35^{-1}\\bmod 3 = 2\\\\\nM_2 &=M/m_2=105/5=21,\\quad t_2=21^{-1}\\bmod 5 = 1\\\\\nM_3 &=M/m_3=105/7=15,\\quad t_3=15^{-1}\\bmod 7 = 1\\\\\n\\end{align}\\] Finally, take these into the general solution formula: \\[x=k⋅105+(2⋅35⋅2+3⋅21⋅1+2⋅15⋅1)=k⋅105+233\\] So the smallest positive integer solution concerning modulus 105 is \\(233\\bmod 105=23\\)

\n

In his mathematical text \"Suanfa Tongzong\", Cheng Dawei, a mathematician of the Ming Dynasty in the 16th century, compiled the solutions recorded by the mathematician Qin Jiushao of the Song Dynasty in the \"Mathematical Treatise in Nine Sections\" into a catchy \"Sun Tzu's Song\":

\n
\n

Three friends set out with seventy rare
\nTwenty-one blossoms on five trees of plums
\nSeven men reunited at the half-month
\nAll be known once divided by one hundred and five

\n
\n

Here we must admire the wisdom of the ancient Chinese who, in the absence of a modern mathematical symbol system, were able to derive and summarize such an ingenious solution, contributing an important mathematical theorem to mankind.

\n\n
\n

So Eve just applies the solution of the Chinese Remainder Theorem, computes \\(M\\), and then finds its cube root to get the plaintext \\(m\\), and the attack succeeds. More generally, setting the number of receivers to \\(k\\), if all receivers use the same \\(e\\), then this broadcast attack is feasible as long as \\(k\\ge e\\).

\n

Håstad further proves that even if padding is used to prevent broadcast attacks, if the messages generated by the padding scheme are linearly related to each other, such as using the formula \\(m_i=i2^b+m\\) (\\(b\\) is the number of bits of \\(m\\)) to generate the message sent to the receiver \\(i\\), then the broadcast attack can still recover the plaintext \\(m\\) as long as \\(k>e\\). The broadcast attack in this case is still based on the Chinese remainder theorem, but the specific cracking method depends on the information of the linear relationship.

\n

To summarize the above analysis, to prevent the broadcast attack, we must use a higher public exponent \\(e\\) and apply random padding at the same time. Nowadays, the common public key exponent \\(e\\) is 65537 (\\(2^{16}+1\\)), which can balance the efficiency and security of message encryption or signature verification operations.

\n

Last, Python routines for simulating broadcast attacks are given as follows:

\n
def solve_crt(ai: list, mi: list):
'''mi and ai are the list of modulus and remainders.
The precondition of the function is that the modulus
in the mi list are pairwise coprime.'''
M = reduce(lambda x, y: x * y, mi)
ti = [a * (M//m) * int(gmpy2.invert(M//m, m)) for (m, a) in zip(mi, ai)]
return reduce(lambda x, y: x + y, ti) % M

def rsa_broadcast_attack(ctexts: list, moduli: list):
'''RSA broadcast attack: applying CRT to crack e=3'''
c0, c1, c2 = ctexts[0], ctexts[1], ctexts[2]
n0, n1, n2 = moduli[0], moduli[1], moduli[2]
m0, m1, m2 = n1 * n2, n0 * n2, n0 * n1
t0 = (c0 * m0 * int(gmpy2.invert(m0, n0)))
t1 = (c1 * m1 * int(gmpy2.invert(m1, n1)))
t2 = (c2 * m2 * int(gmpy2.invert(m2, n2)))
c = (t0 + t1 + t2) % (n0 * n1 * n2)
return int(gmpy2.iroot(c, 3)[0])

def uint_to_bytes(x: int) -> bytes:
'''convert unsigned integer to byte array'''
if x == 0:
return bytes(1)
return x.to_bytes((x.bit_length() + 7) // 8, 'big')

quote = b'The cosmos is within us. We are made of star stuff. - Carl Sagan'
bob = RSA(1024, 3)
carol = RSA(1024, 3)
dave = RSA(1024, 3)
cipher_list = [bob.encrypt(quote), carol.encrypt(quote), dave.encrypt(quote)]
modulus_list = [bob.n, carol.n, dave.n]

cracked_cipher = solve_crt(cipher_list, modulus_list)
cracked_int = int(gmpy2.iroot(cracked_cipher, 3)[0])
assert cracked_int == rsa_broadcast_attack(cipher_list, modulus_list)

hacked_quote = uint_to_bytes(cracked_int)
assert hacked_quote == quote
\n

This code uses two methods to simulate the broadcast attack. One calls the generic Chinese remainder theorem solver function solve_crt() and then gets the cube root of the result; the other calls the special broadcast attack function rsa_broadcast_attack() for the public key index \\(e=3\\), which directly outputs the cracked plaintext value. The internal implementation of these two functions is based on the generalized formula of the Chinese remainder theorem, and the output results should be identical. The cracked plaintext value is then input to the uint_to_bytes() function, which is converted into a byte array to compare with the original quote. Note that the program uses objects generated by the RSA class to simulate the receivers Bob, Carroll, and Dave, and the implementation of the RSA class is omitted here given the limitation of space.

\n
\n

Next article: RSA: Attack and Defense (II)

\n
\n
\n
\n
    \n
  1. American computer scientist and security expert Gary McGraw has a famous piece of advice for software developers - \"never roll your own cryptography\"↩︎

  2. \n
  3. The original RSA paper (Part IX, Section C) did mention Miller's algorithm for factoring \\(N\\) with a known \\(d\\). This algorithm also applies to \\(d\\) generated by the Carmichael function \\(\\lambda(N)\\).↩︎

  4. \n
  5. gmpy2 is a Python extension module written in C that supports multi-precision arithmetic.↩︎

  6. \n
  7. On some special occasions, blinding can be used for effective privacy protection. For example, in cryptographic election systems and digital cash applications, the signer and the message author can be different.↩︎

  8. \n
  9. Johan Håstad, a Swedish theoretical computer scientist, a professor at the KTH Royal Institute of Technology, and a Fellow of the American Mathematical Society (AMS) and an Association for Computing Machinery (ACM) fellow.↩︎

  10. \n
\n
\n","categories":["Technical Know-how"],"tags":["Cryptography","Network Security","Python Programming"]},{"title":"Please Stop Using TLS 1.0 and TLS 1.1 Now!","url":"/en/2022/11/10/Stop-TLS1-0-TLS1-1/","content":"

In March 2021, the Internet Engineering Task Force (IETF) released RFC 8996, classified as a current best practice, officially announcing the deprecation of the TLS 1.0 and TLS 1.1 protocols. If your applications and web services are still using these protocols, please stop immediately and update to TLS 1.2 or TLS 1.3 protocol versions as soon as possible to eliminate any possible security risks.

\n

One single vulnerability is all an attacker needs.
Window Snyder (American computer security expert, former Senior Security Strategist at Microsoft, and has been a top security officer at Apple, Intel and other companies)

\n
\n

RFC Interpretation

\n

The document title of RFC 8996 is quite straightforward, \"Deprecating TLS 1.0 and TLS 1.1\". So what is the rationale it gives? Here is a simple interpretation.

\n

First, take a look at its abstract:

\n
\n

This document formally deprecates Transport Layer Security (TLS) versions 1.0 (RFC 2246) and 1.1 (RFC 4346). Accordingly, those documents have been moved to Historic status. These versions lack support for current and recommended cryptographic algorithms and mechanisms, and various government and industry profiles of applications using TLS now mandate avoiding these old TLS versions. TLS version 1.2 became the recommended version for IETF protocols in 2008 (subsequently being obsoleted by TLS version 1.3 in 2018), providing sufficient time to transition away from older versions. Removing support for older versions from implementations reduces the attack surface, reduces opportunity for misconfiguration, and streamlines library and product maintenance.

\n

This document also deprecates Datagram TLS (DTLS) version 1.0 (RFC 4347) but not DTLS version 1.2, and there is no DTLS version 1.1.

\n

This document updates many RFCs that normatively refer to TLS version 1.0 or TLS version 1.1, as described herein. This document also updates the best practices for TLS usage in RFC 7525; hence, it is part of BCP 195.

\n
\n

The information given here is clear, the reasons for deprecating them are purely technical. TLS 1.0 and TLS 1.1 cannot support stronger encryption algorithms and mechanisms, and cannot meet the high-security requirements of various network applications in the new era. TLS is TCP-based. Corresponding to the UDP-based DTLS protocol, RFC 8996 also announced the deprecation of the DTLS 1.0 protocol.

\n

The Introduction section lists some details of the technical reasons:

\n
    \n
  1. They require the implementation of older cipher suites that are no longer desirable for cryptographic reasons, e.g., TLS 1.0 makes TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA mandatory to implement.
  2. \n
  3. There is a lack of support for current recommended cipher suites, especially authenticated encryption with associated Data (AEAD), which were not supported prior to TLS 1.2.
  4. \n
  5. The integrity of the handshake depends on SHA-1 hash.
  6. \n
  7. The authentication of the peers depends on SHA-1 signatures.
  8. \n
  9. Support for four TLS protocol versions increases the likelihood of misconfiguration.
  10. \n
  11. At least one widely used library has plans to drop TLS 1.1 and TLS 1.0 support in upcoming releases.
  12. \n
\n

Clauses 5 and 6 above are clear and need no further explanation.

\n

For 3DES mentioned in Clause 1, although it uses three independent keys with a total length of 168 bits, considering the possible meet-in-the-middle_attack attack, its effective key strength is only 112 bits. Also, the 3DES encryption block length is still 64 bits, which makes it extremely vulnerable to birthday attack (see Sweet32). NIST stipulates that a single 3DES key group can only be used for encrypting \\(2^{20}\\) data blocks (ie 8MB). This was of course too small, and eventually, NIST decided in 2017 to deprecate 3DES in the IPSec and TLS protocols.

\n

3DES is just one example, another category that has been phased out earlier is cipher suites that use RC4 stream ciphers, see RFC 7465 for details. In addition, there are various problems in the implementation of block cipher CBC mode, which are often exploited by attackers to crack TLS sessions. A summary of various attacks and countermeasures of TLS 1.0 and TLS 1.1 is described in detail in NIST800-52r2 and RFC7457. These two reference documents provide the key rationale for deprecation. Obviously, any protocol that mandates the implementation of insecure cipher suites should be on the list to be eliminated.

\n

In the second section of the document, the content in Section 1.1 \"The History of TLS\" of NIST800-52r2 is directly quoted (abbreviated as shown in the following table):

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
TLS VersionProtocol DocumentKey Feature Update
1.1RFC 4346Improved initialization vector selection and padding error processing to address weaknesses discovered on the CBC mode of operation defined in TLS 1.0.
1.2RFC 5246Enhanced encryption algorithms, particularly in the area of hash functions, can support SHA-2 series algorithms for hashing, MAC, and pseudorandom function computations, also added AEAD cipher suite.
1.3RFC 8446A significant change to TLS that aims to address threats that have arisen over the years. Among the changes are a new handshake protocol, a new key derivation process that uses the HMAC-based Extract-and-Expand Key Derivation Function (HKDF), and the removal of cipher suites that use RSA key transport or static Diffie-Hellman key exchanges, the CBC mode of operation, or SHA-1.
\n

AEAD is an encryption mode that can guarantee the confidentiality, integrity, and authenticity of data at the same time, typically such as CCM and GCM. TLS 1.2 introduced a range of AEAD cipher suites, and its high security made it the exclusive choice for TLS 1.3. These annotate Clause 2 of technical reasons.

\n

Clauses 3 and 4 of technical reasons call out SHA-1, so what is the problem with SHA-1? Section 3 of the document cites a paper by two French researchers, Karthikeyan Bhargavan and Gaetan Leurent .

\n

As a cryptographic hash function, SHA-1 was designed by the National Security Agency (NSA) and then published as a Federal Information Processing Standard (FIPS) by the National Institute of Standards and Technology (NIST). SHA-1 can process a message up to \\(2^{64}\\) bits and generate a 160-bit (20-byte) hash value known as the message digest. Therefore, the complexity of brute force cracking based on birthday attack is \\(2^{80}\\) operations. In 2005, Chinese cryptographer Wang Xiaoyun and her research team made a breakthrough in this field. The high-efficiency SHA-1 attack method they published can be used to find a hash collision within a computational complexity of \\(2^{63}\\). This has brought a huge impact on the security of SHA-1, but it does not mean that the cracking method can enter the practical stage.

\n

Network security protocols (such as TLS, IKE, and SSH, etc.) rely on the second preimage resistance of cryptographic hash functions, that is, it is computationally impossible to find any secondary input value that has the same output as a specific input value. For example, for a cryptographic hash function \\(h(x)\\) and given input \\(x\\), it is difficult to find a sub-preimage \\(x^′ ≠ x\\) that is satisfying \\(h(x) = h(x^′)\\). Because finding a hash collision does not mean that a sub-preimage can be located, in practice, it was once thought that continuing to use SHA-1 is not a problem.

\n

However, in 2016, Bhargavan and Leurent (who implemented the aforementioned Sweet32 attack against 64-bit block ciphers) discovered a new class of methods to attack key exchange protocols that shattered this perception. These methods are based on the principle of the chosen prefix collision attack. That is, given two different prefixes \\(p_1\\) and \\(p_2\\), the attack finds two appendages \\(m_1\\) and \\(m_2\\) such that \\(h(p_1 ∥ m_1) = hash(p_2 ∥ m_2)\\). Using this approach, they demonstrated a man-in-the-middle attack against TLS clients and servers to steal sensitive data, and also showed that the attack could be used to masquerade and downgrade during TLS 1.1, IKEv2, and SSH-2 session handshakes. In particular, they proved that with only \\(2^{77}\\) operations the handshake protocol using SHA-1 or MD5 and SHA-1 concatenated hash values ​​could be cracked.

\n

Since neither TLS 1.0 nor TLS 1.1 allows the peers to choose a stronger cryptographic hash function for signatures in the ServerKeyExchange or CertificateVerify messages, the IETF confirmed that using a newer protocol version is the only upgrade path.

\n

Sections 4 and 5 of the document again clarify that TLS 1.0 and TLS 1.1 must not be used, and negotiation to TLS 1.0 or TLS 1.1 from any TLS version is not allowed. This means that ClientHello.client_version and ServerHello.server_version issued by the TLS client and server, respectively, must not be {03,01} (TLS 1.0) or {03,02} (TLS 1.1). If the protocol version number in the Hello message sent by the other party is {03,01} or {03,02}, the local must respond with a \"protocol_version\" alert message and close the connection.

\n

It is worth noting that due to historical reasons, the TLS specification does not specify the value of the record layer version number (TLSPlaintext.version) when the client sends the ClientHello message. So to maximize interoperability, TLS servers MUST accept any value {03,XX} (including {03,00}) as the record layer version number for ClientHello messages, but they MUST NOT negotiate TLS 1.0 or 1.1.

\n

Section 6 of the document declares a textual revision to the previously published RFC 7525 (Recommendations for the Secure Use of TLS and DTLS). Three places in this RFC change implementation-time negotiations of TLS 1.0, TLS 1.1, and DTLS 1.0 from \"SHOULD NOT\" to \"MUST NOT\". The last section is a summary of standard RFC operations and security considerations.

\n

Industry Responses

\n

In the industry of large public online services, GitHub was the first to act. They started disabling TLS 1.0 and TLS 1.1 in all HTTPS connections back in February 2018, while also phasing out insecure diffie-hellman-group1-sha1 and diffie-hellman-group14-sha1 key exchange algorithms in the SSH connection service. In August 2018, Eric Rescorla, CTO of Mozilla Firefox, published the TLS 1.3 technical specification RFC 8446. Two months later, Mozilla issued a statement together with the three giants of Apple, Google, and Microsoft, and put the deprecation of TLS 1.0 and TLS 1.1 on the agenda.

\n

The following is a brief summary of the actions of several related well-known companies:

\n
    \n
  • Microsoft: For Office 365 services, TLS 1.0 and 1.1 disabling for commercial customers was temporarily suspended due to COVID-19. The mandatory rollout of TLS 1.2 was restarted on October 15, 2020. Users of SharePoint and OneDrive will need to update and configure .NET to support TLS 1.2. Users of Teams Rooms recommend upgrading the app to version 4.0.64.0. The Surface Hub released support for TLS 1.2 in May 2019. The Edge browser version 84 does not use TLS 1.0/1.1 by default, while the Azure cloud computing service will permanently obsolete TLS 1.0/1.1 from March 31, 2022.
  • \n
  • Google: As early as 2018, TLS 1.3 was added to Chrome 70. Starting with Chrome 84, support for TLS 1.0 and TLS 1.1 is completely removed. After running TLS 1.3 in Search Engine, Gmail, YouTube, and various other Google services for some time, TLS 1.3 was officially rolled out in 2020 as the default configuration for all new and existing Cloud CDN and Global Load Balancing customers.
  • \n
  • Apple: Announced in September 2021 that TLS 1.0 and TLS 1.1 will be deprecated in iOS 15, iPadOS 15, macOS 12, watchOS 8, and tvOS 15, and support for them be completely removed in future releases. If the user's application activates the App Transport Security (ATS) feature on all connections, no changes are required. Users are also notified to ensure that the web server supports newer TLS versions and to remove the following deprecated Security.framework symbols from the app\n
      \n
    • tls_protocol_version_t.TLSv10
    • \n
    • tls_protocol_version_t.TLSv11
    • \n
    • tls_protocol_version_t.DTLSv10
    • \n
  • \n
  • Mozilla: Starting with Firefox version 78, the minimum TLS version configured by default is TLS 1.2. In early 2020, Mozilla briefly removed TLS 1.0 and TLS 1.1 from Firefox completely, but this caused many users to be unable to open some COVID-19 outbreak public information sites, so the related functionality had to be restored. Following this, Mozilla provides helpful information on its technical support page, instructing users to modify the minimum TLS version number in the default configuration as needed.
  • \n
  • Cisco: The Cisco Umbrella (renamed from OpenDNS) service discontinued support for all versions of TLS prior to 1.2 on March 31, 2020. After this, only TLS 1.2 compliant clients will be able to connect. In the router and switch product lines, web management has basically been implemented around 2020 to allow only TLS 1.2 or subsequent versions.\n
      \n
    • The CAPWAP connection between Cisco's Wireless Access Point (AP) and Wireless LAN Controller (WLC) is established over DTLS. All 802.11ac Wave 2 and 802.11ax APs from 2015 to the most recent release support DTLS 1.2. The AireOS WLC added DTLS 1.2 functionality in version 8.3.11x.0, and the next-generation C9800 WLC running IOS-XE supports DTLS 1.2 from the start. Note that because of the large number of existing network deployments using older equipment and software versions, DTLS 1.0 support cannot be removed immediately from APs and WLCs at this time to protect user investments. However, DTLS 1.2 is already the default optimal choice for APs and WLCs.
    • \n
  • \n
\n

Protocol Test

\n

Both TLS/DTLS clients and servers need to be tested to verify that their implementations follow the current best practices of RFC 8996.

\n

SSL Lab Test

\n

Qualys originated as a non-commercial SSL Labs Projects. They offer a free and simple client and server testing service, as well as a monitoring panel reporting TLS/SSL security scan statistics for the most popular Internet sites. Below is the most recent chart of protocol support statistics for November 2022.

\n

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Protocol VersionSecuritySupporting Sites (Oct. 2022)Supporting Site (Nov. 2022)% Change
SSL 2.0Insecure316(0.2%)303(0.2%)-0.0%
SSL 3.0Insecure3,015(2.2%)2,930(2.2%)-0.0%
TLS 1.0Deprecated47,450(34.9%)46,691(34.4)-0.5%
TLS 1.1Deprecated51,674(38.1%)50,816(37.5%)-0.6%
TLS 1.2Depending on the Cipher Suite and the Client135,557(99.8)135,445(99.9)+0.1%
TLS 1.3Secure78,479(57.8%)79,163(58.4%)+0.6%
\n

As you can see, almost 100% of sites are running TLS 1.2, and the percentage of TLS 1.3 support is close to 60%. This is very encouraging data. While very few sites are still running SSL 2.0/3.0 and TLS 1.0/1.1 are both still supported at around 35%, overall their percentages are continuing to decline and this good trend should continue.

\n

This blog site is served by GitHub Page, enter the URL to SSL Server Test page and submit it to get a summary of the test results as follows.

\n

\n

The site achieved the highest overall security rating of A+. It got a perfect score for certificate and protocol support, and a 90 for both key exchange and password strength. This shows that GitHub fulfills its security promises to users and deserves the trust of programmers.

\n

The configuration section of the report gives details of the test results for protocol support and cipher suites as follows.

\n

\n

This further confirms that the GitHub Page only supports TLS 1.2/1.3, as required by RFC 8996. It can also be seen that under the \"Cipher Suites\" subheading, TLS 1.3 shows two GCMs and one ChaCha20-Poly1305, which are all cipher suites based on the AEAD algorithms. Three cipher suites of the same type are the preferred TLS 1.2 cipher suites for the server as well. This is exactly the current commonly adopted configuration of secure cryptographic algorithms.

\n

User Selftest

\n

If you suspect that a private server is still using the outdated TLS/SSL protocol, you can do a simple test with the command line tool curl, an example of which is as follows.

\n
❯ curl https://www.cisco.com -svo /dev/null --tls-max 1.1
* Trying 104.108.67.95:443...
* Connected to www.cisco.com (104.108.67.95) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
} [151 bytes data]
* error:1404B42E:SSL routines:ST_CONNECT:tlsv1 alert protocol version
* Closing connection 0
\n

Here enter the command line option -tls-max 1.1 to set the highest protocol version 1.1 and connect to the Cisco home page. The output shows that the connection failed and that a \"protocol version\" alert message was received. This indicates that the server has rejected the TLS 1.1 connection request, and the response is exactly what is required by RFC 8996.

\n

The openssl command line tool provided by the general purpose open source cryptography and secure communication toolkit OpenSSL can also do the same test. To test whether the server supports the TLS 1.2 protocol, use the option s_client to emulate a TLS/SSL client and also enter -tls1_2 to specify that only TLS 1.2 is used. The command line runs as follows.

\n
❯ openssl s_client -connect www.cisco.com:443 -tls1_2
CONNECTED(00000005)
depth=2 C = US, O = IdenTrust, CN = IdenTrust Commercial Root CA 1
verify return:1
depth=1 C = US, O = IdenTrust, OU = HydrantID Trusted Certificate Service, CN = HydrantID Server CA O1
verify return:1
depth=0 CN = www.cisco.com, O = Cisco Systems Inc., L = San Jose, ST = California, C = US
verify return:1
---
Certificate chain
0 s:/CN=www.cisco.com/O=Cisco Systems Inc./L=San Jose/ST=California/C=US
i:/C=US/O=IdenTrust/OU=HydrantID Trusted Certificate Service/CN=HydrantID Server CA O1
1 s:/C=US/O=IdenTrust/OU=HydrantID Trusted Certificate Service/CN=HydrantID Server CA O1
i:/C=US/O=IdenTrust/CN=IdenTrust Commercial Root CA 1
2 s:/C=US/O=IdenTrust/CN=IdenTrust Commercial Root CA 1
i:/C=US/O=IdenTrust/CN=IdenTrust Commercial Root CA 1
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIHrzCCBpegAwIBAgIQQAF9KqwAKOKNhDf17h+WazANBgkqhkiG9w0BAQsFADBy
...
4TY7
-----END CERTIFICATE-----
subject=/CN=www.cisco.com/O=Cisco Systems Inc./L=San Jose/ST=California/C=US
issuer=/C=US/O=IdenTrust/OU=HydrantID Trusted Certificate Service/CN=HydrantID Server CA O1
---
No client certificate CA names sent
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 5765 bytes and written 322 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: 1656D7D14447C1D5E68943F614A697455E60A036957D8D8C18F3B198DF42969F
Session-ID-ctx:
Master-Key: BB1209155344C55792077A4337964661FCA4F3F5BBF3185112F5E235BD07AD63838D24F5CF97161E696CB57398CAF478
TLS session ticket lifetime hint: 83100 (seconds)
TLS session ticket:
0000 - 00 00 0b 33 d4 56 15 3d-64 e8 fa 1d cf c1 1c 04 ...3.V.=d.......
...
0090 - 1b 96 9c 25 82 70 a8 ed-24 1d 70 c9 28 56 84 59 ...%.p..$.p.(V.Y

Start Time: 1653265585
Timeout : 7200 (sec)
Verify return code: 0 (ok)
---
\n

This record is very detailed and the format is very readable. From the output, it can be understood that the digital certificate of the Cisco home page server is digitally signed and certified by the root certificate authority IdenTrust. The client-server session is built on the TLS 1.2 protocol, and the selected cipher suite is ECDHE-RSA-AES128-GCM-SHA256 of type AEAD, which is identical to the preferences provided by the GitHub Page.

\n

Browser Test

\n

If you are not sure about the security of your browser and want to test whether it still supports the pre-TLS 1.2 protocols, you can enter the following URL in your browser's address bar.

\n\n

After connecting to the second URL with the default configuration of Firefox, the page shows the following

\n
\n

Secure Connection Failed

\n

An error occurred during a connection to tls-v1-1.badssl.com:1011. Peer using unsupported version of security protocol.

\n

Error code: SSL_ERROR_UNSUPPORTED_VERSION

\n
    \n
  • The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.
  • \n
  • Please contact the website owners to inform them of this problem.
  • \n
\n

This website might not support the TLS 1.2 protocol, which is the minimum version supported by Firefox.

\n
\n

This error message clearly indicates that Firefox is running a minimum TLS protocol version of 1.2 in this configuration, and since the other side is only running TLS 1.1, the two sides cannot establish a connection.

\n

So what is the result of the connection when the browser does still retain TLS 1.0/1.1 functionality?

\n

For testing purposes, you can first change the default TLS preference value of Firefox to 1.1 by following the steps below (refer to the figure below).

\n
    \n
  1. Open a new tab, type about:config in the address bar, and press Enter/Return.
  2. \n
  3. The page prompts \"Proceed with Caution\", click the Accept the Risk and Continue button.
  4. \n
  5. In the search box at the top of the page, type TLS to display the filtered list.
  6. \n
  7. Find the security.tls.version.min preference option and click the Edit icon to change the minimum TLS version.\n
      \n
    • TLS 1.0 => 1
    • \n
    • TLS 1.1 => 2
    • \n
    • TLS 1.2 => 3
    • \n
    • TLS 1.3 => 4
    • \n
  8. \n
\n

\n

At this point, then connect to https://tls-v1-1.badssl.com, the result is

\n

\n

This bold red page tells you that the browser you are currently using does not have TLS 1.1 disabled and is a security risk, so try not to use it if you can.

\n

After testing, don't forget to restore the default TLS minimum version setting (3) for Firefox.

\n

References

\n
\n

Disclosure: This blog site is reader-supported. When you buy through the affiliate links below, as an Amazon Associate, I earn a tiny commission from qualifying purchases. Thank you.

\n
\n

Besides NIST and RFC documents, For an in-depth study of the TLS protocol specification, system implementation, and application deployment, a careful reading of the following three books is recommended.

\n
\n
    \n
  1. SSL and TLS: Theory and Practice, Second Edition (2016) - This book provides a comprehensive discussion of the SSL, TLS, and DTLS protocols. It has complete details on the theory and practice of the protocols, offering readers a solid understanding of their design principles and modes of operation. The book also presents the advantages and disadvantages of the protocols compared to other Internet security protocols and provides the details necessary to correctly implement the protocols while saving time on the security practitioner’s side.
  2. \n
  3. Implementing SSL/TLS Using Cryptography and PKI (2011) - For a network professional who knows C programming, this book is a hands-on, practical guide to implementing SSL and TLS protocols for Internet security. Focused on how to implement SSL and TLS, it walks you through all the necessary steps, whether or not you have a working knowledge of cryptography. The book covers TLS 1.2, including implementations of the relevant cryptographic protocols, secure hashing, certificate parsing, certificate generation, and more.
  4. \n
  5. Bulletproof TLS and PKI, Second Edition: Understanding and Deploying SSL/TLS and PKI to Secure Servers and Web (2022) - This book is a complete guide to using TLS encryption and PKI to deploy secure servers and web applications. Written by Ivan Ristić, founder of the popular SSL Labs website, it will teach you everything you need to know to protect your systems from eavesdropping and impersonation attacks. You can also find just the right mix of theory, protocol detail, vulnerability and weakness information, and deployment advice to get the work done.
  6. \n
\n","categories":["Technology Review"],"tags":["Cryptography","Network Security"]},{"title":"TLS 1.3 and the Coming NIST Mandate","url":"/en/2023/08/21/TLS1-3-intro/","content":"

TLS (Transport Layer Security) is a cryptographic protocol to secure network communication. TLS 1.3 is the latest version of the TLS protocol, succeeding TLS 1.2. TLS 1.3 aims to provide more robust security, higher privacy protection, as well as better performance than previous versions. Here is a brief introduction to TLS 1.3. Also, we discuss NIST's requirement for TLS 1.3 readiness and give examples of enabling TLS 1.3 in some commonly used web servers.

\n

It takes 20 years to build a reputation and a few minutes of cyber-incident to ruin it.
Stéphane Nappo (Vice President and Global Chief Information Security Officer of Groupe SEB, France, 2018 Global CISO of the year)

\n
\n

Introduction to TLS 1.3

\n

TLS 1.3 is the latest recommended cryptographic protocol for protecting a wide variety of network communications, including web browsing, email, online trading, instant messaging, mobile payments, and many other applications. By using TLS 1.3, more secure and reliable communication connections can be established, ensuring confidentiality, authenticity, and data integrity. It was standardized by the Internet Engineering Task Force (IETF) in August 2018, and published as RFC 8446.

\n

TLS 1.3 introduces some important improvements over TLS 1.2. The table below presents a quick comparison of the two:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
AspectTLS 1.2TLS 1.3
Protocol DesignRequest-response modelReduced round trips
HandshakeMultiple round tripsSingle round trip
Cipher SuitesSupports wide range, including insecure onesFocuses on stronger algorithms
SecurityKnown vulnerabilities, e.g., CBC vulnerabilitiesAddresses previous issues, stronger security
PerformanceHigher latency due to more round tripsFaster connection establishment
Resilience to AttacksVulnerable to downgrade attacks and padding oracle attacksAdditional protections against attacks
CompatibilityWidely supported across platformsIncreasing support, may not be available on older systems
Implementation SupportsAvailable in many cryptographic librariesSupported in various libraries
\n

It can be seen that enhanced security and performance improvements are the most notable features of TLS 1.3, and we can explore more into these in the following sections.

\n

Security Hardening

\n

Cipher Suites

\n

The protocol design principle of TLS 1.3 has enhanced security as its primary goal. As a result, TLS 1.3 drastically reduces the number of supported cipher suites. It removes insecure and weak cipher suites, leaving only more secure and modern cipher suites. This helps to increase the security of communications and avoids the use of outdated or vulnerable cipher suites.

\n

Specifically, TLS 1.3 removes various cipher suites that use static RSA key transport, static Diffie-Hellman key exchange, CBC mode of operation, or SHA-1. It adopts only a limited number of Authenticated Encryption with Associated Data (AEAD) cipher suites. AEAD can guarantee the confidentiality, integrity, and authenticity of data at the same time, and its high security makes it the exclusive choice for TLS 1.3.

\n

On the other hand, the name string of the cipher suite used in previous TLS versions included all algorithms for key exchange, digital signatures, encryption, and message authentication. Each cipher suite is assigned a 2-byte code point in the TLS Cipher Suites registry managed by the Internet Assigned Numbers Authority (IANA). Every time a new cryptographic algorithm is introduced, a series of new combinations need to be added to the list. This has led to an explosion of code points representing every valid choice of these parameters. This situation also makes the selection of cipher suites complicated and confusing.

\n

The design of TLS 1.3 changed the concept of the cipher suite. It separates the authentication and key exchange mechanisms from the record protection algorithm (including secret key length) and a hash to be used with both the key derivation function and handshake message authentication code (MAC). The new cipher suite naming convention is TLS_<AEAD>_<Hash>, where the hash algorithm is used for the newly defined key derivation function HKDF of TLS 1.3 and the MAC generation in the handshake phase. The cipher suites defined by the TLS 1.3 protocol are:

\n
RFC 8446 - Appendix B.4. Cipher Suites
+------------------------------+-------------+
| Description | Value |
+------------------------------+-------------+
| TLS_AES_128_GCM_SHA256 | {0x13,0x01} |
| | |
| TLS_AES_256_GCM_SHA384 | {0x13,0x02} |
| | |
| TLS_CHACHA20_POLY1305_SHA256 | {0x13,0x03} |
| | |
| TLS_AES_128_CCM_SHA256 | {0x13,0x04} |
| | |
| TLS_AES_128_CCM_8_SHA256 | {0x13,0x05} |
+------------------------------+-------------+
\n

This simplified cipher suite definition and greatly reduced set of negotiation parameters also speed up TLS 1.3 handshake, improving overall performance.

\n

Key Exchange

\n

TLS 1.3 emphasizes forward secrecy, ensuring that the confidentiality of communications is protected even if long-term secrets used in the session key exchange are compromised. It only allows key exchange based on ephemeral Diffie-Hellman key exchange (DHE) or ephemeral elliptic curve Diffie-Hellman key exchange (ECDHE). Both have the property of forward secrecy. Also, the protocol explicitly restricts the use of secure elliptic curve groups and finite field groups for key exchange:

\n
/* Elliptic Curve Groups (ECDHE) */
secp256r1(0x0017), secp384r1(0x0018), secp521r1(0x0019),
x25519(0x001D), x448(0x001E),

/* Finite Field Groups (DHE) */
ffdhe2048(0x0100), ffdhe3072(0x0101), ffdhe4096(0x0102),
ffdhe6144(0x0103), ffdhe8192(0x0104),
\n

The above elliptic curve groups for ECDHE are specified by RFC 8422. The first three are defined by the FIPS.186-4 specification and the corresponding NIST names are P-256/P-384/P-512, while the next two (x25519/x448) are recommended by ANSI.X9-62.2005. RFC 7919 specifies four finite field groups (ffdhe####) for DHE. The primes in these finite field groups are all safe primes.

\n

In number theory, a prime number \\(p\\) is a safe prime if \\((p-1)/2\\) is also prime.

\n
\n

Signature Verification

\n

For signature verification in the key exchange phase, TLS 1.3 introduces more signature algorithms to meet different security requirements:

\n
    \n
  • RSA signature algorithm: TLS 1.3 still supports RSA-based signature algorithms, including RSA-PKCS1-SHA256, RSA-PKCS1-SHA384, etc. These algorithms use RSA keys for digital signatures.
  • \n
  • ECDSA signature algorithm: TLS 1.3 introduces more signature algorithms based on elliptic curve cryptography (ECC), such as ECDSA-SHA256, ECDSA-SHA384, etc. These algorithms use elliptic curve keys for digital signatures and are generally superior to RSA in terms of security and performance.
  • \n
  • EdDSA signature algorithm: TLS 1.3 also introduces the EdDSA (Edwards-curve Digital Signature Algorithm) signature algorithm based on the Edwards curve. It features efficient performance and strong security for mobile devices and resource-constrained environments.
  • \n
  • RSASSA-PSS signature algorithm: In addition to the traditional RSA-PKCS1 signature algorithm, TLS 1.3 also introduces the RSASSA-PSS signature algorithm, which is a more secure signature method based on RSA and has better attack resistance.
  • \n
  • PSK signature algorithm: TLS 1.3 supports the signature algorithm based on the pre-shared key (PSK), which applies to the PSK handshake mode. This approach does not involve a digital certificate but uses a pre-shared key for verification.
  • \n
\n

TLS 1.3 stops using the DSA (Digital Signature Algorithm) signature algorithm. This is also a notable difference from TLS 1.2. DSA has some security and performance limitations and is rarely used in practice, so TLS 1.3 removed support for DSA certificates.

\n

Other Reinforcements

\n

Additionally, TLS 1.3 includes the following improvements to enhance security

\n
    \n
  • TLS 1.3 does not allow data compression. The data compression feature in earlier versions of TLS could lead to security issues such as CRIME attacks. To avoid this risk, TLS 1.3 removed support for data compression entirely.
  • \n
  • Unlike earlier versions of TLS, TLS 1.3 prohibits renegotiation after the connection has been established. This helps reduce security risk and complexity. Renegotiation may introduce new security holes, and frequent negotiations during the connection process may also cause performance problems.
  • \n
  • All handshake messages following the ServerHello message during the TLS 1.3 handshake are now encrypted. The newly introduced EncryptedExtensions message enables encryption protection of various extensions previously sent in plain text.
  • \n
  • TLS 1.3 adds asymmetric cryptographic protection of the Certificate messages sent from the server to the client. This encryption prevents threats such as man-in-the-middle attacks, information leakage, and certificate forgery, further fortifying the security and privacy of the connection.
  • \n
\n

Performance Boosting

\n

Simplified Handshake

\n

The general trend towards high-speed mobile Internet requires the use of HTTPS/TLS to protect the privacy of all traffic as much as possible. The downside of this is that new connections can become a bit slower. For the client and web server to agree on a shared key, both parties need to exchange security attributes and related parameters through the TLS \"handshake process\". In TLS 1.2 and all protocols before it, the initial handshake process required at least two round-trip message transfers. Compared to pure HTTP, the extra latency introduced by the TLS handshake process of HTTPS can be very detrimental to performance-conscious applications.

\n

TLS 1.3 greatly simplifies the handshake process, requiring only one round trip in most cases, resulting in faster connection establishment and lower latency. Every TLS 1.3 connection will use (EC)DHE-based key exchange, and the parameters supported by the server may be easy to guess (such as ECDHE + x25519 or P-256). Since the options are limited, the client can directly send the (EC)DHE key share information in the first message without waiting for the server to confirm which key exchange it is willing to support. This way, the server can derive the shared secret one round in advance and send encrypted data.

\n

The following diagram compares the message sequences of the handshake process of TLS 1.2 and TLS 1.3. Both operate with public key-based authentication. The TLS 1.3 handshake shown below uses the symbols borrowed from the RFC 8446 specification: '+' indicates a noteworthy extension; '*' indicates an optional message or extension; '[]', '()', and '{}' represent encrypted messages, where the keys used for encryption are different.

\n
\n\"TLS
TLS 1.2 handshake (left) vs. TLS 1.3 handshake (right)
\n
\n

This figure illustrates the following points:

\n
    \n
  • TLS 1.3 removes several messages used by TLS 1.2: ServerHelloDone, ChangeCipherSpec, ServerKeyExchange, and ClientKeyExchange. The contents of TLS 1.2's ServerKeyExchange and ClientKeyExchange messages vary depending on the authentication and key-sharing method being negotiated. In TLS 1.3, this information was moved to the extensions of ClientHello and ServerHello messages. TLS 1.3 completely deprecates ServerHelloDone and ChangeCipherSpec messages, there is no replacement.
  • \n
  • For TLS 1.3 the public key-based authentication mode is probably the most important. It always uses (EC)DHE to achieve forward secrecy. The figure shows that the ClientHello message carries four extensions that are must-haves in this mode: key_share, signature_algorithms, supported_groups, and support_versions.
  • \n
  • During the TLS 1.2 handshake, the exchange of control data requires multiple round trips between the client and server. TLS 1.2's ClientKeyExchange and ChangeCipherSpec messages are carried in separate packets, and the Finished message is the first (and only) encrypted handshake message. The whole process needs to transmit 5-7 data packets.
  • \n
  • During the TLS 1.3 handshake, encrypted Application Data is already sent by the client after the first round trip. As mentioned earlier, the EncryptedExtension message provides privacy protection for ServerHello extensions in earlier versions of TLS. If mutual authentication is required (which is common in IoT deployments), the server will send a CertificateRequest message.
  • \n
  • The Certificate, CertificateVerify, and Finished messages in TLS 1.3 retain the semantics of earlier TLS versions, but they are all asymmetrically encrypted now. Echoing the description in the last section, by encrypting Certificate and CertificateVerify messages, TLS 1.3 better protects against man-in-the-middle and certificate forgery attacks while enhancing the privacy of connections. This is also an important security feature in the design of TLS 1.3.
  • \n
\n

In rare cases, when the server does not support a certain key-sharing method sent by the client, the server can send a new HelloRetryRequest message letting the client know which groups it supports. As the group list has shrunk significantly, this is not expected to happen very often.

\n

0-RTT Session Resumption

\n

0-RTT (Zero Round Trip Time) in TLS 1.3 is a special handshake mode. It allows clients to send encrypted data during the handshake phase, reducing the number of round trips required for connection establishment and enabling faster session resumption. The following is a brief explanation of the 0-RTT working mode:

\n
    \n
  1. Store session tickets: During the normal TLS 1.3 handshake, the client and server generate a data structure called a \"session ticket\" during the handshake. Session tickets contain information about the connection, including key parameters and cipher suites. The server stores the session ticket provided by the client.
  2. \n
  3. 0-RTT handshake: When the client reconnects to the server, it includes the previously saved session ticket in the early_data extension of the ClientHello message, along with encrypted Application Data. The client encrypts 0-RTT data using a pre-shared key (PSK) obtained from a previous connection.
  4. \n
  5. Server Response: After the server receives this message, if it supports 0-RTT mode and can recognize and verify the session ticket, it sends an EncryptedExtensions message, and then confirms the connection in the Finished message. This way, the server can quickly establish a secure connection with 0 round trips. It can also immediately send data to the client to achieve 0-RTT data transmission.
  6. \n
\n

The message sequence of the 0-RTT session resumption and data transmission process of TLS 1.3 is as follows:

\n
\n\"TLS
TLS 1.3 0-RTT
\n
\n

FAQ

\n
    \n
  • Does the TLS 1.3 protocol allow the use of RSA digital certificates?

    \n

    A common misconception is that \"TLS 1.3 is not compatible with RSA digital certificates\". The description in the \"Signature Verification\" section above shows that this is wrong. TLS 1.3 still supports the use of RSA for key exchange and authentication. However, considering the limitations of RSA, it is recommended that when building and deploying new TLS 1.3 applications, ECDHE key exchange algorithms and ECC digital certificates are preferred to achieve higher security and performance.

  • \n
  • During the TLS 1.3 handshake, how does the server request the client to provide a certificate?

    \n

    In some scenarios, the server also needs to verify the identity of the client to ensure that only legitimate clients can access server resources. This is the case with mTLS (mutual TLS). During the TLS 1.3 handshake, the server can specify that the client is required to provide a certificate by sending a special CertificateRequest extension. When the server decides to ask the client for a certificate, it sends a CertificateRequest extension message after the ServerHello message. This extended message contains some necessary parameters, such as a list of supported certificate types, a list of acceptable certificate authorities, and so on. When the client receives it, it knows that the server asked it for a certificate, and it can optionally respond to the request. If the client is also configured to support mTLS and decides to provide a certificate, it provides its certificate chain by sending a Certificate message.

  • \n
  • Is 0-RTT vulnerable to replay attacks?

    \n

    TLS 1.3's 0-RTT session resumption mode is non-interactive and does risk replay attacks in some cases. An attacker may repeat previously sent data to simulate a legitimate request. To avoid and reduce the risk of replay attacks to the greatest extent, TLS 1.3 provides some protection measures and suggestions:

    \n
      \n
    1. The simplest anti-replay method is that the server only allows each session ticket to be used once. For example, the server may maintain a database of all valid tickets that have not been used, deleting each ticket from the database as it is used. If an unknown ticket is received, the server falls back to a full handshake.
    2. \n
    3. The server may limit the time window in which session tickets are accepted, that is, the time range in which 0-RTT data is allowed to be valid. This reduces the chance of an attacker successfully replaying.
    4. \n
    5. Clients and servers should also use 0-RTT data only for stateless requests, that is, requests that do not affect the state of the server such as HTTP GET. For requests that need to modify the state of the server or have an impact, restrict the use of normal handshake patterns only.
    6. \n
    7. Another way to prevent replay is to store the unique value (usually a random value or a PSK bundled value) derived from the ClientHello message, and reject duplicates. Logging all ClientHellos would cause the state to grow without bound, but combined with #2 above, the server can log ClientHellos within a given time window and use obfuscated_ticket_age to ensure that tickets are not duplicated outside the window use.
    8. \n
  • \n
  • If the client does not know whether the server supports TLS 1.3, how could it negotiate the TLS version via handshake?

    \n

    The TLS protocol provides a built-in mechanism for negotiating the running version between endpoints. TLS 1.3 continues this tradition. RFC 8446 Appendix D.1 \"Negotiating with an Older Server\" gives specific instructions:

    \n
    \n

    A TLS 1.3 client who wishes to negotiate with servers that do not support TLS 1.3 will send a normal TLS 1.3 ClientHello containing 0x0303 (TLS 1.2) in ClientHello.legacy_version but with the correct version(s) in the \"supported_versions\" extension. If the server does not support TLS 1.3, it will respond with a ServerHello containing an older version number. If the client agrees to use this version, the negotiation will proceed as appropriate for the negotiated protocol.

    \n
    \n

    The following screenshot of a TLS 1.3 ClientHello message decode demonstrates this. The version number of the handshake message displayed on the left is \"Version: TLS 1.2 (0x0303)\". At the same time, it can be seen that the cipher suite section first lists 3 TLS 1.3 AEAD cipher suites, followed by 14 TLS 1.2 regular cipher suites. On the right, there are 4 extensions - key_share, signature_algorithms, supported_groups, and support_versions. The support_versions extension includes both TLS 1.3 and TLS 1.2 version numbers. This is the TLS version list for the server to choose from. Additionally, the key_share extension includes the client's preferred key-sharing method as x25519 and secp256r1(i.e. NIST P-256)

    \n

  • \n
  • Does the TLS 1.3 protocol work with UDP and EAP?

    \n

    TLS was originally designed for TCP connections, and a variant DTLS (Datagram Transport Layer Security) for UDP was introduced later. Based on TLS 1.3, IETF has released the corresponding upgraded version of the DTLS 1.3 protocol RFC 9147. The design goal of DTLS 1.3 is to provide \"equivalent security guarantees with the exception of order protection / non-replayability\". This protocol was released in April 2022, and currently, there are not many software libraries supporting it.

    \n

    TLS can also be used as an authentication and encryption protocol in various EAP types, such as EAP-TLS, EAP-FAST, and PEAP. Corresponding to TLS 1.3, IETF also published two technical standard documents:

    \n
      \n
    • RFC 9190: EAP-TLS 1.3: Using the Extensible Authentication Protocol with TLS 1.3 (Feb. 2022)
    • \n
    • RFC 9427: TLS-Based Extensible Authentication Protocol (EAP) Types for Use with TLS 1.3 (Jun. 2023)
    • \n
    \n

    Both protocols are also quite new, and the software library updates supporting them are still some time away.

  • \n
\n

NIST Mandate

\n

TLS 1.3 brings new security features and a faster TLS handshake. Since its release in 2018, many Internet services have migrated to this latest version. Nevertheless, widespread adoption across websites takes time. The non-commercial SSL Labs Projects has a dashboard called SSL Pulse that reports TLS/SSL security scan statistics for the most popular Internet sites. Below is the most recent chart of protocol support statistics by July 2023.

\n
\n\"Source:
Source: SSL Pulse - 07/03/2023
\n
\n

As can be seen, of all 135,000+ probed sites the percentage of TLS 1.3 support is about 63.5%. That means there are still close to 50 thousand sites that do not leverage the security and performance benefits of TLS 1.3. Why? The decision to migrate a website to a new protocol version like TLS 1.3 can be complex and influenced by various factors. The top 3 common reasons hindering TLS 1.3 migration are

\n
    \n
  • Compatibility Concerns: Some websites might have users who are still using outdated browsers or operating systems that do not support TLS 1.3. These websites need to maintain backward compatibility to ensure that all users can access their content securely.
  • \n
  • Resource Constraints: Migration involves technical updates, configuration changes, and testing. Smaller websites or those with limited resources might face challenges in allocating the necessary time and effort to make these changes.
  • \n
  • Third-Party Dependencies: Many websites rely on third-party services, content delivery networks, or other components. If these services do not yet support TLS 1.3, the website might delay migration to avoid disruptions or compatibility issues with these dependencies.
  • \n
\n

However, for network hardware/software vendors who want their products on the procurement list of any US public sector organization, there is a coming NIST mandate to make TLS 1.3 available by January 2024. This is stipulated in the National Institute of Standards and Technology Special Publication (NIST SP) 800-52 Rev. 2: Guidelines for the Selection, Configuration, and Use of Transport Layer Security (TLS) Implementations. Quoted from NIST SP 800-52 Rev. 2

\n
\n

3.1 Protocol Version Support

\n

Servers that support government-only applications shall be configured to use TLS 1.2 and should be configured to use TLS 1.3 as well. ...

\n

Servers that support citizen or business-facing applications (i.e., the client may not be part of a government IT system)10 shall be configured to negotiate TLS 1.2 and should be configured to negotiate TLS 1.3. ...

\n

Agencies shall support TLS 1.3 by January 1, 2024. After this date, servers shall support TLS 1.3 for both government-only and citizen or business-facing applications. In general, servers that support TLS 1.3 should be configured to use TLS 1.2 as well. However, TLS 1.2 may be disabled on servers that support TLS 1.3 if it has been determined that TLS 1.2 is not needed for interoperability.

\n
\n

As in the RFC documents, \"shall\" above is a strong keyword that means that the definition is an absolute requirement of the specification. So this NIST publication requires all servers owned by the US government agencies to be able to support TLS 1.3 by 01/01/2024. They must run a minimum TLS version 1.2 by default and can be configured to do TLS 1.3 only if desired.

\n

It is worth pointing out that this is not an official FIPS requirement, so not mandatory for the FIPS 140-3 certification at present. Besides, this NIPS document has a clear scope statement: \"The scope is further limited to TLS when used in conjunction with TCP/IP. For example, Datagram TLS (DTLS), which operates over datagram protocols, is outside the scope of these guidelines. NIST may issue separate guidelines for DTLS at a later date.\" Based on this, we can infer that DTLS and EAP are out of consideration for this mandate.

\n

Enabling TLS 1.3

\n

The enhanced security and optimized performance of TLS 1.3 make it the first choice for securing communication of various network applications. Now we demonstrate how to enable TLS 1.3 function in three commonly used web server software Apache, Nginx, and Lighttpd.

\n

NOTE: The implementation of many secure network communication applications relies on third-party SSL/TLS software libraries, such as wolfSSL, GnuTLS, NSS, and OpenSSL. Therefore, to enable the TLS 1.3 function of these applications, you need to ensure that the libraries they link with support TLS 1.3. For example, in September 2018, the popular OpenSSL project released version 1.1.1 of the library, with support for TLS 1.3 as its \"top new feature\".

\n
\n

Apache HTTP Server

\n

The Apache HTTP Server is an open-source web server software from the Apache Software Foundation. Apache HTTP server is widely used and is one of the most popular web server software due to its cross-platform and security. Apache supports a variety of features, many of which extend core functionality through compiled modules, such as authentication schemes, proxy servers, URL rewriting, SSL/TLS support, and compiling interpreters such as Perl/Python into the server.

\n

Apache HTTP Server has built-in support for TLS 1.3 since version 2.4.36, no need to install any additional modules or patches. The following command can be used to verify the version of the server

\n
$ apache2ctl -v 
Server version: Apache/2.4.41 (Ubuntu)
Server built: 2020-04-13T17:19:17
\n

Once the version is verified, the SSLProtocol line of the configuration file can be updated. The following will enable the Apache HTTP server to only support the TLS 1.3 protocol

\n
/etc/apache2/mods-available/ssl.conf
# Only enable TLS 1.3
SSLProtocol -all +TLSv1.3
\n

If the server needs to be compatible with clients that support TLS 1.2, you can add +TLSv1.2. After updating the configuration, restart the service

\n
$ sudo service apache2 restart
\n

Nginx Web Server

\n

Nginx is a high-performance web server based on an asynchronous framework and modular design. It can also be used for reverse proxy, load balancer, and HTTP caching applications. It is free and open-source software released under the terms of a BSD-like license. Nginx uses an asynchronous event-driven approach to request processing, which can provide more predictable performance under high load. The current market share of Nginx is almost equal to that of the Apache HTTP server.

\n

Nginx supports TLS 1.3 from version 1.13.0. The following command can be used to verify its version

\n
$ nginx -v
nginx version: nginx/1.17.10 (Ubuntu)
\n

In the Nginx configuration file, find the server block and modify the ssl_protocols line to enable TLS 1.3:

\n
/etc/nginx/nginx.conf
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
root /var/www/example.com/public;

ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private-key.key;

# support TLS 1.2 and TLS 1.3
ssl_protocols TLSv1.2 TLSv1.3;

...
}
\n

If you don't need to continue to support TLS 1.2, delete the TLSv1.2 there. After the modification is complete, you can run the following command to test the configuration of Nginx, and then restart the service

\n
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

$ sudo service nginx restart
\n

Lighttpd Web Server

\n

Lighttpd is a lightweight open-source web server software. It focuses on high performance, low memory footprint, and fast responsiveness. Lighttpd is suitable for serving web applications and static content of all sizes. Its design goal is to provide an efficient, flexible, and scalable web server, especially suitable for high-load and resource-constrained (such as embedded systems) environments.

\n

The first Lighttpd release to support TLS 1.3 is version 1.4.56. Starting with this version, the minimum version of TLS that Lighttpd supports by default is TLS 1.2. That is to say, Lighttpd supports TLS 1.2 and TLS 1.3 if no corresponding configuration file modification is made.

\n

To limit the use of Lighttpd to only the TLS 1.3 feature, first make sure the mod_openssl module is loaded. Then in the configuration file lighttpd.conf, find the server.modules section, and add the following ssl.openssl.ssl-conf-cmd line:

\n
/etc/lighttpd/lighttpd.conf
server.modules += ("mod_openssl")
$SERVER["socket"] == ":443" {
ssl.engine = "enable"
ssl.pemfile = "/path/to/your/cert.pem"
ssl.privkey = "/path/to/your/privkey.pem"
ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.3",
"Options" => "-ServerPreference")
}
\n

This will set the minimum version supported by Lighttpd to be TLS 1.3. Finally, save and reload the Lighttpd configuration for the changes to take effect:

\n
sudo lighttpd -t -f /etc/lighttpd/lighttpd.conf # check configuration
sudo systemctl reload lighttpd
\n","categories":["Technical Know-how"],"tags":["Cryptography","Network Security"]},{"title":"Notes on Using uClibc Standard Library in Embedded Linux System","url":"/en/2023/03/10/uClibc-tips/","content":"

uClibc is a small and exquisite C standard library for embedded Linux systems. It is widely used in the development of low-end embedded systems and Internet of Things devices. Here are some recent experiences to provide convenience for engineers who need to solve similar problems or meet corresponding requirements.

\n

Low-level programming is good for the programmer's soul.
John Carmack (American computer programmer and video game developer, co-founder of the video game company id Software)

\n
\n

Introduction to uClibc

\n

uClibc (sometimes written as μClibc) is a small C standard library designed to provide support for embedded systems and mobile devices using operating systems based on the Linux kernel. uClibc was originally developed to support μClinux, a version of Linux not requiring a memory management unit thus especially suited for microcontroller systems. The \"uC\" in its name is the abbreviation of microcontroller in English, where \"u\" is a Latin script typographical approximation of the Greek letter μ that stands for \"micro\".

\n

uClibc is a free and open-source software licensed under the GNU Lesser GPL, and its library functions encapsulate the system calls of the Linux kernel. It can run on standard or MMU-less Linux systems and supports many processors such as i386, x86-64, ARM, MIPS, and PowerPC. Development of uClibc started in 1999 and was written mostly from scratch, but also absorbed code from glibc and other projects. uClibc is much smaller than glibc. While glibc aims to fully support all relevant C standards on a wide range of hardware and kernel platforms, uClibc focuses on embedded Linux systems. It also allows developers to enable or disable some features according to the memory space design requirements.

\n

The following records show the list of C standard library files in two similar embedded systems. The first uses glibc-2.23 version, and the second integrates uClibc-0.9.33.2 version. The total size of glibc library files is more than 2MB, while the uClibc library files add up to less than 1MB. It can be seen that using uClibc does save a lot of storage space.

\n
STM1:/# find . -name "*lib*2.23*" | xargs ls -alh
-rwxr-xr-x 1 root root 9.6K Jan 1 1970 ./lib/libanl-2.23.so
-rwxr-xr-x 1 root root 1.1M Jan 1 1970 ./lib/libc-2.23.so
-rwxr-xr-x 1 root root 177.5K Jan 1 1970 ./lib/libcidn-2.23.so
-rwxr-xr-x 1 root root 29.5K Jan 1 1970 ./lib/libcrypt-2.23.so
-rwxr-xr-x 1 root root 9.5K Jan 1 1970 ./lib/libdl-2.23.so
-rwxr-xr-x 1 root root 429.4K Jan 1 1970 ./lib/libm-2.23.so
-rwxr-xr-x 1 root root 65.8K Jan 1 1970 ./lib/libnsl-2.23.so
-rwxr-xr-x 1 root root 17.5K Jan 1 1970 ./lib/libnss_dns-2.23.so
-rwxr-xr-x 1 root root 33.6K Jan 1 1970 ./lib/libnss_files-2.23.so
-rwxr-xr-x 1 root root 90.5K Jan 1 1970 ./lib/libpthread-2.23.so
-rwxr-xr-x 1 root root 65.7K Jan 1 1970 ./lib/libresolv-2.23.so
-rwxr-xr-x 1 root root 25.9K Jan 1 1970 ./lib/librt-2.23.so
-rwxr-xr-x 1 root root 9.5K Jan 1 1970 ./lib/libutil-2.23.so

STM2:/# find . -name "*lib*0.9.33*" | xargs ls -alh
-rwxr-xr-x 1 root root 28.0K Jan 1 1970 ./lib/ld-uClibc-0.9.33.2.so
-rwxr-xr-x 1 root root 36.1K Jan 1 1970 ./lib/libcrypt-0.9.33.2.so
-rwxr-xr-x 1 root root 16.2K Jan 1 1970 ./lib/libdl-0.9.33.2.so
-rwxr-xr-x 1 root root 72.1K Jan 1 1970 ./lib/libm-0.9.33.2.so
-rwxr-xr-x 1 root root 116.4K Jan 1 1970 ./lib/libpthread-0.9.33.2.so
-rwxr-xr-x 1 root root 16.2K Jan 1 1970 ./lib/librt-0.9.33.2.so
-rwxr-xr-x 1 root root 28.3K Jan 1 1970 ./lib/libthread_db-0.9.33.2.so
-rwxr-xr-x 1 root root 621.4K Jan 1 1970 ./lib/libuClibc-0.9.33.2.so
-rwxr-xr-x 1 root root 8.1K Jan 1 1970 ./lib/libubacktrace-0.9.33.2.so
-rwxr-xr-x 1 root root 4.1K Jan 1 1970 ./lib/libutil-0.9.33.2.so
\n

IPv6 and Interface API

\n

With the steady growth of IPv6 deployment, adding IPv6 protocol stack support for embedded systems has become necessary. In a software project that adds IPv4/IPv6 dual-stack function to devices using uClibc, it is found that there is an application link error - undefined reference to getifaddrs. getifaddrs() is a very useful function, we can call it to get the address information of all the network interfaces of the system. Query the Linux programming manual:

\n
SYNOPSIS
#include <sys/types.h>
#include <ifaddrs.h>

int getifaddrs(struct ifaddrs **ifap);
...
\t
DESCRIPTION
The getifaddrs() function creates a linked list of structures
describing the network interfaces of the local system, and stores
the address of the first item of the list in *ifap.
...

VERSIONS
The getifaddrs() function first appeared in glibc 2.3, but before
glibc 2.3.3, the implementation supported only IPv4 addresses;
IPv6 support was added in glibc 2.3.3. Support of address
families other than IPv4 is available only on kernels that
support netlink.
...
\n

The last sentence above is key: only kernels supporting netlink can support address families other than IPv4. The Linux kernel version running on this system is 3.x, which supports netlink. So, could there be a problem with uClibc's support for netlink that causes getifaddrs() not to get compiled?

\n

With this question in mind, search the source code directory of uClibc and find the C file that implements the function getifaddrs():

\n
libc/inet/ifaddrs.c
...
#if __ASSUME_NETLINK_SUPPORT
#ifdef __UCLIBC_SUPPORT_AI_ADDRCONFIG__
/* struct to hold the data for one ifaddrs entry, so we can allocate
everything at once. */
struct ifaddrs_storage
{
struct ifaddrs ifa;
union
{
/* Save space for the biggest of the four used sockaddr types and
avoid a lot of casts. */
struct sockaddr sa;
struct sockaddr_ll sl;
struct sockaddr_in s4;
#ifdef __UCLIBC_HAS_IPV6__
struct sockaddr_in6 s6;
#endif
} addr, netmask, broadaddr;
char name[IF_NAMESIZE + 1];
};
#endif /* __UCLIBC_SUPPORT_AI_ADDRCONFIG__ */
...
#ifdef __UCLIBC_SUPPORT_AI_ADDRCONFIG__
...
int
getifaddrs (struct ifaddrs **ifap)
...
#endif /* __UCLIBC_SUPPORT_AI_ADDRCONFIG__ */
...
#endif /* __ASSUME_NETLINK_SUPPORT */
\n

Just as expected! The implementation of the entire function and the definition of the associated data structure ifaddrs_storageare are placed under three nested conditional compilation directives with macros defined as

\n
    \n
  1. __ASSUME_NETLINK_SUPPORT
  2. \n
  3. __UCLIBC_SUPPORT_AI_ADDRCONFIG__
  4. \n
  5. __UCLIBC_HAS_IPV6__
  6. \n
\n

Therefore, as long as their corresponding configuration lines are opened, the problem should be solved. After changing the configuration file of uClibc as follows, rebuild the dynamic link library of uClibc, then the application can be made successfully:

\n
--- a/toolchain/uClibc/config-0.9.33.2/common
+++ b/toolchain/uClibc/config-0.9.33.2/common
@@ -147,7 +147,8 @@ UCLIBC_HAS_RPC=y
UCLIBC_HAS_FULL_RPC=y
-# UCLIBC_HAS_IPV6 is not set
+UCLIBC_HAS_IPV6=y
-# UCLIBC_USE_NETLINK is not set
+UCLIBC_USE_NETLINK=y
+UCLIBC_SUPPORT_AI_ADDRCONFIG=y
UCLIBC_HAS_BSD_RES_CLOSE=y
\n

SHA-2 Hash Function

\n

Embedded systems often need to provide remote SSH login services for system administrators, which requires the creation of system users and their passwords. Linux saves the user name and the hashed password in the /etc/shadow file. The storage format of the hash value follows a de facto standard called the Modular Crypt Format (MCF for short), and its format is as follows:

\n
$<id>[$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
\n

Here

\n
    \n
  • id: indicates the identifier of the hash algorithm (eg 1 for MD5, 5 for SHA-256, 6 for SHA-512)
  • \n
  • param=value: Hash complexity parameters (such as the number of rounds/iterations) and their values
  • \n
  • salt: radix-64 (charset [+/a-zA-Z0-9]) encoded salt
  • \n
  • hash: the radix-64 encoded hash result of the password and salt
  • \n
\n

With the rapid increase of computing power following Moore's Law, the previously commonly used MD5-based hashing scheme has become obsolete because it is too vulnerable to attack. Newly designed systems are now switched to the SHA-512 hashing scheme, corresponding to $6$ seen in the /etc/shadow file.

\n

Both generation and verification of user password hash values ​​can be implemented with the POSIX C library function named crypt. This function is defined as follows:

\n
char *crypt(const char *key, const char *salt)
\n

The input parameter key points to the string containing the user's password, and salt points to a string in the format $<id>$<salt> indicating the hash algorithm and salt to be used. Most Linux distributions use the crypt function provided by the glibc library. The following figure summarizes the augmented crypt function in Glibc:

\n

\n

In an embedded Linux system integrating uClibc, uClibc provides support for the crypt function. But the test found that it returned a null pointer for the correct \\(6\\) input! What's going on here?

\n

The answer lies in the uClibc's implementation of the crypt function. Find the corresponding C source code:

\n
libcrypt/crypt.c
#include <unistd.h>
#include <crypt.h>
#include "libcrypt.h"

char *crypt(const char *key, const char *salt)
{
const unsigned char *ukey = (const unsigned char *)key;
const unsigned char *usalt = (const unsigned char *)salt;

if (salt[0] == '$') {
if (salt[1] && salt[2] == '$') { /* no blowfish '2X' here ATM */
if (*++salt == '1')
return __md5_crypt(ukey, usalt);
#ifdef __UCLIBC_HAS_SHA256_CRYPT_IMPL__
else if (*salt == '5')
return __sha256_crypt(ukey, usalt);
#endif
#ifdef __UCLIBC_HAS_SHA512_CRYPT_IMPL__
else if (*salt == '6')
return __sha512_crypt(ukey, usalt);
#endif
}
/* __set_errno(EINVAL);*/ /* ENOSYS might be misleading */
return NULL;
}
return __des_crypt(ukey, usalt);
}
\n

Aha! It turns out that it only does MD5 hashing by default, and the codes of SHA-256 and SHA-512 need their own conditional compilation macro definitions. This is easy to handle, just edit the configuration file of uClibc and open the latter two.

\n
--- a/toolchain/uClibc/config-0.9.33.2/common
+++ b/toolchain/uClibc/config-0.9.33.2/common
@@ -151,8 +151,8 @@ UCLIBC_HAS_REGEX_OLD=y
UCLIBC_HAS_RESOLVER_SUPPORT=y
-# UCLIBC_HAS_SHA256_CRYPT_IMPL is not set
-# UCLIBC_HAS_SHA512_CRYPT_IMPL is not set
+UCLIBC_HAS_SHA256_CRYPT_IMPL=y
+UCLIBC_HAS_SHA512_CRYPT_IMPL=y
UCLIBC_HAS_SHADOW=y
\n

Finally, take a look at the program that comes with uClibc to test the SHA-512 hash algorithm. It clearly lists the data structures defined by the test code, including the salt, the input password, and the expected output, as well as several test vectors:

\n
test/crypt/sha512c-test.c
static const struct
{
const char *salt;
const char *input;
const char *expected;
} tests[] =
{
{ "$6$saltstring", "Hello world!",
"$6$saltstring$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJu"
"esI68u4OTLiBFdcbYEdFCoEOfaS35inz1" },
{ "$6$rounds=10000$saltstringsaltstring", "Hello world!",
"$6$rounds=10000$saltstringsaltst$OW1/O6BYHV6BcXZu8QVeXbDWra3Oeqh0sb"
"HbbMCVNSnCM/UrjmM0Dp8vOuZeHBy/YTBmSK6H9qs/y3RnOaw5v." },
...
{ "$6$rounds=10$roundstoolow", "the minimum number is still observed",
"$6$rounds=1000$roundstoolow$kUMsbe306n21p9R.FRkW3IGn.S9NPN0x50YhH1x"
"hLsPuWGsUSklZt58jaTfF4ZEQpyUNGc0dqbpBYYBaHHrsX." },
};
\n

It can be seen that the last test case defines the round value 10 ($6$rounds=10$roundstoolow), while the output shows that the round is 1000 (rounds=1000). This confirms that the crypt function implementation of uClibc matches the augmented function of Glibc - in order to ensure security, if the input specified round is too small, crypt will automatically set to the minimum round of 1000.

\n

DNS Security Patch

\n

In early May 2022, Nozomi Networks, a company focused on providing security solutions for industrial and critical infrastructure environments, released a newly discovered uClibc security vulnerability CVE-2022-30295. This vulnerability exists in the Domain Name System (DNS) implementation of all versions of uClibc and its fork uClibc-ng (prior to version 1.0.41). Since the implementation uses predictable transaction IDs when making DNS requests, there is a risk of DNS cache poisoning attacks.

\n

Specifically, applications often call gethostbyname library functions to resolve a network address for a given hostname. uClibc/uClibc-ng internally implements a __dns_lookup function for the actual DNS domain name request and response processing. Taking the last version 0.9.33.2 of uClibc as an example, the screenshot below shows the problematic code in the function __dns_lookup:

\n

\n

Referring to line 1308, at the first DNS request, the variable local_id is initialized to the transaction ID value of the last DNS request (stored in a static variable last_id). Line 1319 is the actual culprit, it simply updates the old local_id value by incrementing it by 1. This new value is stored back into the variable last_id, as shown on line 1322. Finally, on line 1334, the value of local_id is copied into the structure variable h, which represents the actual content of the DNS request header. This code works pretty much in all available versions of uClibc and uClibc-ng prior to version 1.0.41.

\n

This implementation makes the transaction ID in the DNS request predictable, because the attacker can estimate the value of the transaction ID in the next request as long as he/she detects the current transaction ID. By exploiting this vulnerability, an attacker can disrupt/poison the host's DNS cache by crafting a DNS response containing the correct source port and winning the competition with the legitimate response returned by the DNS server, making the network data of the application in the host system be directed to a trap site set by the attacker.

\n

The maintainers of uClibc-ng responded quickly to the announcement of this security vulnerability. They submitted a fix in mid-May 2022, and released version 1.0.41 including this patch at the end of that month. For uClibc, since this C standard library has stopped releasing any new versions since 2012, it is currently in an unmaintained state, so system R&D engineers need to come up with their repair. The following uClibc patches are available for reference:

\n
diff --git a/libc/inet/resolv.c b/libc/inet/resolv.c
index 31e63810b..c2a8e2be4 100644
--- a/libc/inet/resolv.c
+++ b/libc/inet/resolv.c
@@ -315,6 +315,7 @@ Domain name in a message can be represented as either:
#include <sys/utsname.h>
#include <sys/un.h>
#include <sys/stat.h>
+#include <fcntl.h>
#include <sys/param.h>
#include <bits/uClibc_mutex.h>
#include "internal/parse_config.h"
@@ -1212,6 +1213,20 @@ static int __decode_answer(const unsigned char *message, /* packet */
return i + RRFIXEDSZ + a->rdlength;
}

+uint16_t dnsrand_next(int urand_fd, int def_value) {
+ if (urand_fd == -1) return def_value;
+ uint16_t val;
+ if(read(urand_fd, &val, 2) != 2) return def_value;
+ return val;
+}
+
+int dnsrand_setup(int *urand_fd, int def_value) {
+ if (*urand_fd > 0) return dnsrand_next(*urand_fd, def_value);
+ *urand_fd = open("/dev/urandom", O_RDONLY);
+ if (*urand_fd == -1) return def_value;
+ return dnsrand_next(*urand_fd, def_value);
+}
+
/* On entry:
* a.buf(len) = auxiliary buffer for IP addresses after first one
* a.add_count = how many additional addresses are there already
@@ -1237,6 +1252,7 @@ int __dns_lookup(const char *name,
/* Protected by __resolv_lock: */
static int last_ns_num = 0;
static uint16_t last_id = 1;
+ static int urand_fd = -1;

int i, j, fd, rc;
int packet_len;
@@ -1305,7 +1321,7 @@ int __dns_lookup(const char *name,
}
/* first time? pick starting server etc */
if (local_ns_num < 0) {
- local_id = last_id;
+ local_id = dnsrand_setup(&urand_fd, last_id);
/*TODO: implement /etc/resolv.conf's "options rotate"
(a.k.a. RES_ROTATE bit in _res.options)
local_ns_num = 0;
@@ -1316,8 +1332,9 @@ int __dns_lookup(const char *name,
retries_left--;
if (local_ns_num >= __nameservers)
local_ns_num = 0;
- local_id++;
+ local_id = dnsrand_next(urand_fd, local_id++);
local_id &= 0xffff;
+ DPRINTF("local_id:0x%hx\\n", local_id);
/* write new values back while still under lock */
last_id = local_id;
last_ns_num = local_ns_num;
\n

This uClibc patch is a simplified version of the uClibc-ng official patch. Its core is to read a double-byte random number from the system /dev/urandom file, and then use it to set the original local_id, the transaction ID of the DNS request. /dev/urandom is a special device file of the Linux system. It can be used as a non-blocking random number generator, which will reuse the data in the entropy pool to generate pseudo-random data.

\n

Note that in the above patch, the function dnsrand_setup must first check urand_fd whether it is positive, and only open /dev/urandom when it is not true. Otherwise, the file will be reopened every time the application does a DNS lookup, the system will quickly hit the maximum number of file descriptors allowed, and the system will crash because it cannot open any more files.

\n

Finally, a comparison of an embedded system using uClibc before and after adding DNS security patches is given. The following are the DNS packets intercepted by two sniffers. In the first unpatched system, the transaction ID of the DNS request is incremented in sequence, which is an obvious security hole; the second is after the patch is added, the transaction ID of each DNS request is a random value, and the loophole has been filled.

\n

\n","categories":["Technical Know-how"],"tags":["C/C++ Programming","System Programming","Cryptography","TCP/IP","Computer Communications"]},{"title":"Solve picoCTF's RSA Challenge Sum-O-Primes","url":"/en/2022/08/20/picoCTF-Sum-O-Primes/","content":"

By chance, I came across a picoCTF RSA challenge called Sum-O-Primes. This problem is not difficult, you can do it by knowing the basics of the RSA algorithm. In addition, if you are familiar with the history of the evolution of the RSA algorithm, you can find a second ingenious fast solution.

\n

picoCTF Project

\n

picoCTF is a free computer security education program created by security and privacy experts at Carnegie Mellon University. It uses original content built on the CTF (Capture the Flag) framework to provide a variety of challenges. It provides participants with valuable opportunities to systematically learn cybersecurity knowledge and gain practical experience.

\n

The collection of practice questions for picoCTF is called picoGym. The general problem solution is to search or decipher a string in the format \"picoCTF{...}\" from the given information, that is, the flag to be captured. As shown in the figure below, picoGym currently contains 271 cybersecurity challenge exercises, covering general skills, cryptography, reverse engineering, forensics, and other fields.

\n

\n

Sum-O-Primes Challenge

\n

There are 50 cryptography-related challenges in picoGym, one of which is Sum-O-Primes. The task of this challenge is simple and explained as follows:

\n
\n

We have so much faith in RSA we give you not just the product of the primes, but their sum as well!

\n\n
\n

That is, we not only give the product of the two prime numbers used by RSA but also tell you their sum. How are these given? You need to discover by yourself from the rest of the information. After clicking the two links and downloading the file, open the first Python file:

\n
gen.py
#!/usr/bin/python

from binascii import hexlify
from gmpy2 import mpz_urandomb, next_prime, random_state
import math
import os
import sys

if sys.version_info < (3, 9):
import gmpy2
math.gcd = gmpy2.gcd
math.lcm = gmpy2.lcm

FLAG = open('flag.txt').read().strip()
FLAG = int(hexlify(FLAG.encode()), 16)
SEED = int(hexlify(os.urandom(32)).decode(), 16)
STATE = random_state(SEED)

def get_prime(bits):
return next_prime(mpz_urandomb(STATE, bits) | (1 << (bits - 1)))

p = get_prime(1024)
q = get_prime(1024)

x = p + q
n = p * q

e = 65537

m = math.lcm(p - 1, q - 1)
d = pow(e, -1, m)

c = pow(FLAG, e, n)

print(f'x = {x:x}')
print(f'n = {n:x}')
print(f'c = {c:x}')
\n

If you have basic Python programming skills and understand the principles of the RSA algorithm, you should be able to read the above program quickly. What it does is:

\n
    \n
  1. Open the file flag.txt to read the content. Then use the hexlify and int functions to convert it to an integer and store the result in a variable FLAG.
  2. \n
  3. Call the function get_prime to generate two prime numbers, store their sum in x and their product in n. Then assign 65537 to e and calculate the RSA private exponent d.
  4. \n
  5. Use standard pow functions to perform modular exponentiation, which implements RSA encryption to encrypt plaintext FLAG into ciphertext c.
  6. \n
  7. Print out x, n, and c.
  8. \n
\n

Open the second file, which is apparently the output of the first program in Python:

\n
output.txt
x = 154ee809a4dc337290e6a4996e0717dd938160d6abfb651736d9f5d524812a659b310ad1f221196ee8ab187fa746a1b488a4079cddfc5db08e78be0d96c83c01e9bb42420b40d6f0ad9f220633459a6dc058bb01c517386bfbd2d4811c9b08558b0e05534768581a74884758d15e15b4ef0dbd6a338bf1f52eed4f137957737d2
n = 6ce91e471f1df651b0d275d6d5522703feecdd77e7821a2caf9514104c059781c1b2e64772d9220addd657ecbd4e6cb8b5941608f6ab54bd5760074a5cd5854920439422192d2ee8912f1ebcc0d97714f209ee2a22e2da60e071541cb7e0772373cfea71831673378ee6432e63abfd14db0d4aa601928923253f9edd419ce96f4d68ce0aa3e6d6b530cd46eefbdac93038ce949c9dd2e573a47471cf8223f88b96e00a92f4d47fd277c42c4075b5e99b41a9f279f442bc0d533b9ddc50592e369e7026b3f7afaa8edf8972f0c3055f4de67a0eea963f099a32e1539de1d1727abadd9235f66371998ec883d1f89b8d907270842818cae49cd5c7f906c4752e81
c = 48b89662b9718fb391c96527272bf74c27810edaca09b63e694af9d11608010b1db9aedd1c867849371121941a1ccac610f7b28b92fa2f981babe816e6d3ecfab83514ed7e18e2b23fc3b96c7002ff47da897e9f2a9cb1b4e245396589e0b72affb73568a2016031555d2a46557919e44a15cd43fe9e1881d40dce1d1e36625e63b1472d3c317898102943072e06d79688c96b6ee2e584002c66497a9cdc48c38aa0548a7bc4fed9b4c23fcd493f38ece68788ef37a559b7f20c6941fcf8e567d9f50807259a7f11fa7a01d3125a1f7609cd94781f224ec8351605354b11c6b078fe015826342c3271ee3af4b99bb0a538b1e6b845594ee6546be8abd22ef2bd
\n

Once you understand the meaning of the question, you can make a judgment immediately —— if you can decrypt the ciphertext c and retrieve the plaintext FLAG, you can get the original content of flag.txt, that is, capture the flag.

\n

Conventional Solution

\n

RSA decryption requires a private key exponent d. Referring to the steps of the RSA algorithm below, it is obvious that this demands integer factorization for large prime numbers p and q first.

\n
    \n
  1. Choose two large prime numbers \\(p\\) and \\(q\\), compute \\(n=pq\\)
  2. \n
  3. Compute Carmichael function \\(\\lambda(n)=\\operatorname{lcm}(p − 1, q − 1)\\) the product, \\(\\operatorname{lcm}\\) is a function to find the least common multiple
  4. \n
  5. Choose any number \\(e\\) that is less than and coprime to \\(\\lambda(n)\\), then compute \\(d\\), the modular multiplicative inverse of \\(e\\) regarding \\(\\lambda(n)\\), \\(d\\equiv e^{-1}\\pmod {\\lambda(n)}\\)
  6. \n
  7. \\((n,e)\\) is the RSA public key, \\((n,d)\\) the RSA private key
  8. \n
  9. Use the public key to encrypt the plaintext \\(m\\), the formula is \\(c\\equiv m^e\\pmod n\\)
  10. \n
  11. Use the private key to decrypt the ciphertext \\(c\\), the formula is \\(m\\equiv c^d\\pmod n\\)
  12. \n
\n

From here, the challenge becomes a problem that, knowing the sum and product of two large prime numbers known, find these two large prime numbers. That is, to solve a system of quadratic linear equations

\n

\\[\n\\left\\{\n\\begin{aligned}\np+q &=n \\\\ \np*q &=x\n\\end{aligned} \n\\right. \n\\]

\n

Using the knowledge of elementary mathematics, the above equations can be transformed into a quadratic equation \\[p^2 - x * p + n = 0\\]

\n

Obviously, \\(p\\) and \\(q\\) are its two roots. According to the quadratic formula

\n

\\[(p,q)={\\frac {x}{2}}\\pm {\\sqrt {\\left({\\frac {x}{2}}\\right)^{2}-n}}\\]

\n

We can get \\(p\\) and \\(q\\). The rest of the work is easy. The code to compute \\(d\\) from \\(p\\) and \\(q\\) can be copied directly from lines 28, 30, and 31 in gen.py. The final complete Python problem-solving code is as follows:

\n
import math

file = open('output.txt', 'r')
Lines = file.readlines()
file.close()

x = int((Lines[0].split())[2], 16) # x = p + q
n = int((Lines[1].split())[2], 16) # n = p * q
c = int((Lines[2].split())[2], 16) # Ciphertext

def solve_rsa_primes(s: int, m: int) -> tuple:
'''
Solve RSA prime numbers (p, q) from the quadratic equation
p^2 - s * p + m = 0 with the formula p = s/2 +/- sqrt((s/2)^2 - m)

Input: s - sum of primes, m - product of primes
Output: (p, q)
'''
half_s = s >> 1
tmp = math.isqrt(half_s ** 2 - m)
return int(half_s + tmp), int(half_s - tmp);

# Now run with the real input
p, q = solve_rsa_primes(x, n)
m = math.lcm(p - 1, q - 1)
e = 65537
d = pow(e, -1, m)
FLAG = pow(c, d, n)
print(FLAG.to_bytes((FLAG.bit_length() + 7) // 8, 'big'))
\n

The above program defines a general function solve_rsa_primes to solve two large prime numbers. After it gets d, the same pow function is called to decrypt, and finally the plaintext is converted from a large integer to a byte sequence and printed out. The result of running this program is

\n
b'picoCTF{pl33z_n0_g1v3_c0ngru3nc3_0f_5qu4r35_92fe3557}'
\n

BINGO! Capture the Flag successfully!

\n

Note: The function solve_rsa_primes calls math.isqrt to compute the integer square root of the given integer. This is indispensable! If it is written incorrectly with math.sqrt, the following overflow error will occur

\n
>>>
=============== RESTART: /Users/zixi/Downloads/Sum-O-Primes.py ==============
Traceback (most recent call last):
File "/Users/zixi/Downloads/Sum-O-Primes.py", line 35, in <module>
p, q = solve_rsa_primes(x, n)
File "/Users/zixi/Downloads/Sum-O-Primes.py", line 31, in solve_rsa_primes
tmp = math.sqrt(int(half_s ** 2 - m))
OverflowError: int too large to convert to float
\n

This error happens because math.sqrt uses floating-point arithmetic but fails to convert large integers to floating-point numbers.

\n
\n

Quick Solution

\n

The conventional solution to this problem has to solve a quadratic equation, so the integer square root operation is essential. Is there a solution that doesn't need a square root operation? The answer is yes.

\n

In the original RSA paper, the public exponent \\(e\\) and the private exponent \\(d\\) have the relationship as the following equation

\n

\\[d⋅e≡1\\pmod{\\varphi(n)}\\]

\n

Here the modular is the Euler's totient function \\(\\varphi(n)=(p-1)(q-1)\\). Since \\(\\varphi(N)\\) is always divisible by \\(\\lambda(n)\\), any d satisfying the above also satisfies \\(d⋅e≡1\\pmod{\\lambda(n)}\\), thus the private exponent is not unique. Although the calculated \\(d>\\lambda(n)\\), the square root operation can be avoided when applied to the Sum-O-Primes problem. This is because \\[\n\\begin{aligned}\n\\varphi(n)&=(p-1)(q-1)\\\\\n&=pq-(p+q)+1\\\\\n&=n-x+1\n\\end{aligned}\n\\]

\n

Hereby the formula for computing the private exponent becomes

\n

\\[\n\\begin{aligned}\nd&≡e^{-1}\\pmod{\\varphi(n)}\\\\\n&≡e^{-1}\\pmod{(n-x+1)}\n\\end{aligned}\n\\]

\n

Now that \\(n\\) and \\(x\\) are readily available, this method does not require finding \\(p\\) and \\(q\\) first, and naturally, there is no need for a square root operation. The Python code for this new solution is very concise

\n
d1 = pow(e, -1, n - x + 1)
FLAG = pow(c, d1, n)
print(FLAG.to_bytes((FLAG.bit_length() + 7) // 8, 'big'))

print("d = ", d)
print("d1 = ", d1)
assert(d1>d)
print("d1/d = ", d1/d)
\n

To compare these two solutions, 4 lines of print and assert statements are added at the end. The execution result of this code is

\n
>>>
=============== RESTART: /Users/zixi/Downloads/Sum-O-Primes.py ==============
b'picoCTF{pl33z_n0_g1v3_c0ngru3nc3_0f_5qu4r35_92fe3557}'
d = 1590433953643304448870807755026766943237397482033766155980367645454600169745357277163199312196609495875891431590581528929277583062406061101224041553945564552302546648687338536694903918084325519368961617691238793972703013656395301935576994660878296156727353260699130612675943209520489312860964899655070852366584778594425834982623831654304915478835573020874834723387183369976749895237126850604587166433366381884290402338703266523462767765540527102747754912478720160791675179128443712374832507705614160658601242723842366612805686436771142338154848447759947887908800687914418476358484536216953925324788380823429735298973
d1 = 11901952834426939436403812982514571575614906347331071933175950931208083895179963694981295931167346168378938101218143770786299673201984563299831132533757316974157649670783507276616478666261648674806749337918514985951832847720617452268824430679672778783943236259522437088812130196067329355430038927225825521934485847159262037514154059696664148362902872186817856316128403800463106817000251243818717005827615275821709043532925457271839955998044684537152992871171338447136672661193487297988293156428071068861346467230927990425182893890027896377626007826573834588309038513191969376781172191621785853174152547091371818954913
d1/d = 7.483462489694971
\n

As shown above, this solution also succeeds in capturing the flag. The \\(d\\) value (d1) calculated by the new solution is more than 7 times that of the conventional solution.

\n

Click here to download all the code of this article: Sum-O-Primes.py.gz

\n","categories":["Technical Know-how"],"tags":["Cryptography","Python Programming","CTF"]}] \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index 7ebfc15..68d1b65 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -4,7 +4,7 @@ https://www.packetmania.net/en/2024/04/18/Purdue-MA265-2022-Spring-Final/ - 2024-04-26 + 2024-04-27 monthly 0.6 @@ -265,7 +265,7 @@ https://www.packetmania.net/en - 2024-04-26 + 2024-04-27 daily 1.0 @@ -273,91 +273,91 @@ https://www.packetmania.net/en/tags/C-C-Programming/ - 2024-04-26 + 2024-04-27 weekly 0.2 https://www.packetmania.net/en/tags/System-Programming/ - 2024-04-26 + 2024-04-27 weekly 0.2 https://www.packetmania.net/en/tags/Cryptography/ - 2024-04-26 + 2024-04-27 weekly 0.2 - https://www.packetmania.net/en/tags/Network-Security/ - 2024-04-26 + https://www.packetmania.net/en/tags/TCP-IP/ + 2024-04-27 weekly 0.2 - https://www.packetmania.net/en/tags/Computer-Architecture/ - 2024-04-26 + https://www.packetmania.net/en/tags/Network-Security/ + 2024-04-27 weekly 0.2 - https://www.packetmania.net/en/tags/Computer-Communications/ - 2024-04-26 + https://www.packetmania.net/en/tags/Computer-Architecture/ + 2024-04-27 weekly 0.2 - https://www.packetmania.net/en/tags/TCP-IP/ - 2024-04-26 + https://www.packetmania.net/en/tags/Computer-Communications/ + 2024-04-27 weekly 0.2 https://www.packetmania.net/en/tags/Cisco-Technology/ - 2024-04-26 + 2024-04-27 weekly 0.2 https://www.packetmania.net/en/tags/Linear-Algebra/ - 2024-04-26 + 2024-04-27 weekly 0.2 - https://www.packetmania.net/en/tags/Python-Programming/ - 2024-04-26 + https://www.packetmania.net/en/tags/Raspberry-Pi/ + 2024-04-27 weekly 0.2 - https://www.packetmania.net/en/tags/Raspberry-Pi/ - 2024-04-26 + https://www.packetmania.net/en/tags/NAS/ + 2024-04-27 weekly 0.2 - https://www.packetmania.net/en/tags/NAS/ - 2024-04-26 + https://www.packetmania.net/en/tags/Python-Programming/ + 2024-04-27 weekly 0.2 https://www.packetmania.net/en/tags/CTF/ - 2024-04-26 + 2024-04-27 weekly 0.2 @@ -366,35 +366,35 @@ https://www.packetmania.net/en/categories/Tool-Guide/ - 2024-04-26 + 2024-04-27 weekly 0.2 https://www.packetmania.net/en/categories/Study-Notes/ - 2024-04-26 + 2024-04-27 weekly 0.2 - https://www.packetmania.net/en/categories/Technical-Know-how/ - 2024-04-26 + https://www.packetmania.net/en/categories/DIY-Projects/ + 2024-04-27 weekly 0.2 - https://www.packetmania.net/en/categories/DIY-Projects/ - 2024-04-26 + https://www.packetmania.net/en/categories/Technical-Know-how/ + 2024-04-27 weekly 0.2 https://www.packetmania.net/en/categories/Technology-Review/ - 2024-04-26 + 2024-04-27 weekly 0.2 diff --git a/tags/C-C-Programming/index.html b/tags/C-C-Programming/index.html index 30d4ac1..89e476a 100644 --- a/tags/C-C-Programming/index.html +++ b/tags/C-C-Programming/index.html @@ -483,14 +483,14 @@

C/C++ Programming Symbols count total: - 436k + 441k

diff --git a/tags/CTF/index.html b/tags/CTF/index.html index 101be22..3bfe237 100644 --- a/tags/CTF/index.html +++ b/tags/CTF/index.html @@ -294,14 +294,14 @@

CTF Symbols count total: - 436k + 441k

diff --git a/tags/Cisco-Technology/index.html b/tags/Cisco-Technology/index.html index 29a37b1..ff72e52 100644 --- a/tags/Cisco-Technology/index.html +++ b/tags/Cisco-Technology/index.html @@ -294,14 +294,14 @@

Cisco Technology Symbols count total: - 436k + 441k

diff --git a/tags/Computer-Architecture/index.html b/tags/Computer-Architecture/index.html index 00b7767..a7b4275 100644 --- a/tags/Computer-Architecture/index.html +++ b/tags/Computer-Architecture/index.html @@ -294,14 +294,14 @@

Computer Architecture Symbols count total: - 436k + 441k

diff --git a/tags/Computer-Communications/index.html b/tags/Computer-Communications/index.html index d33278e..093e998 100644 --- a/tags/Computer-Communications/index.html +++ b/tags/Computer-Communications/index.html @@ -317,14 +317,14 @@

Computer Communications Symbols count total: - 436k + 441k

diff --git a/tags/Cryptography/index.html b/tags/Cryptography/index.html index 0caf4d8..58484cb 100644 --- a/tags/Cryptography/index.html +++ b/tags/Cryptography/index.html @@ -457,14 +457,14 @@

Cryptography Symbols count total: - 436k + 441k

diff --git a/tags/Linear-Algebra/index.html b/tags/Linear-Algebra/index.html index 744f21f..3fe9e75 100644 --- a/tags/Linear-Algebra/index.html +++ b/tags/Linear-Algebra/index.html @@ -414,14 +414,14 @@

Linear Algebra Symbols count total: - 436k + 441k

diff --git a/tags/NAS/index.html b/tags/NAS/index.html index 39c7d9e..94c8496 100644 --- a/tags/NAS/index.html +++ b/tags/NAS/index.html @@ -294,14 +294,14 @@

NAS Symbols count total: - 436k + 441k

diff --git a/tags/Network-Security/index.html b/tags/Network-Security/index.html index 54ecb72..9f99fdc 100644 --- a/tags/Network-Security/index.html +++ b/tags/Network-Security/index.html @@ -377,14 +377,14 @@

Network Security Symbols count total: - 436k + 441k

diff --git a/tags/Python-Programming/index.html b/tags/Python-Programming/index.html index 89565c2..3feed9f 100644 --- a/tags/Python-Programming/index.html +++ b/tags/Python-Programming/index.html @@ -357,14 +357,14 @@

Python Programming Symbols count total: - 436k + 441k

diff --git a/tags/Raspberry-Pi/index.html b/tags/Raspberry-Pi/index.html index 1601033..0d4ebf0 100644 --- a/tags/Raspberry-Pi/index.html +++ b/tags/Raspberry-Pi/index.html @@ -294,14 +294,14 @@

Raspberry Pi Symbols count total: - 436k + 441k

diff --git a/tags/System-Programming/index.html b/tags/System-Programming/index.html index 0488ddb..79f0e9f 100644 --- a/tags/System-Programming/index.html +++ b/tags/System-Programming/index.html @@ -340,14 +340,14 @@

System Programming Symbols count total: - 436k + 441k

diff --git a/tags/TCP-IP/index.html b/tags/TCP-IP/index.html index 5e857dd..8e1de4b 100644 --- a/tags/TCP-IP/index.html +++ b/tags/TCP-IP/index.html @@ -340,14 +340,14 @@

TCP/IP Symbols count total: - 436k + 441k

diff --git a/tags/index.html b/tags/index.html index 4055c38..e8347e5 100644 --- a/tags/index.html +++ b/tags/index.html @@ -289,14 +289,14 @@

Tags Symbols count total: - 436k + 441k