An IPv4 address is one 32-bit number. Here is how to read it as decimal, hex, binary or integer, size a CIDR block, and spot private ranges.
Password entropy measures how hard a secret is to guess, in bits. Here is the formula, why Password1! fails it, and how to generate something strong.
9 min read
A hash is a fixed-length fingerprint of data. Here is how MD5 and SHA-256 differ, why fast hashes are wrong for passwords, and what HMAC adds.
10 min read
An IPv4 address is one 32-bit number. RFC 791 says it plainly: addresses are "fixed length of four octets (32 bits)." Dotted decimal like 192.168.1.1 is just the friendliest way to print that number. Once that clicks, the odd forms in logs, databases and URLs stop being mysterious, and so does CIDR.
Need a conversion now? The free IP converter swaps an IPv4 address between dotted decimal, hex, binary and integer. For "what kind of address is this?", use IP Address Info. For a prefix like
/24, the Subnet Calculator does the math.
Every output below was captured on 9 October 2026 (IST) on a Linux box with Python 3.13.5 and Node 20. Examples use private, loopback and documentation ranges only.
Same value, four spellings. Python's standard ipaddress module prints them side by side:
python3 -c "import ipaddress as i
for s in ('127.0.0.1', '192.168.1.1'):
a = i.ip_address(s)
print(s, int(a), hex(int(a)), '.'.join(f'{o:08b}' for o in a.packed))
print(i.ip_address(2130706433))"
127.0.0.1 2130706433 0x7f000001 01111111.00000000.00000000.00000001
192.168.1.1 3232235777 0xc0a80101 11000000.10101000.00000001.00000001
127.0.0.1
| Form | 127.0.0.1 | 192.168.1.1 |
|---|---|---|
| Dotted decimal | 127.0.0.1 | 192.168.1.1 |
| Unsigned 32-bit integer | 2130706433 | 3232235777 |
| Hex | 0x7f000001 | 0xc0a80101 |
| Binary octets | 01111111.00000000.00000000.00000001 | 11000000.10101000.00000001.00000001 |
It is 127.0.0.1, the loopback address, written as one integer. Each octet is a base-256 digit, most significant first:
127 × 256³ + 0 × 256² + 0 × 256 + 1 = 2,130,706,432 + 1 = 2130706433
Going back, divide by 256 and keep the remainders, or let the last line of the run above do it. If an integer like this turns up in a log, paste it into IP Address Info and it will tell you it's loopback.
INET_ATON() returns an address as an integer in network byte order. Its docs give 10.0.5.9 as 167773449 and say to use INT UNSIGNED, because a signed column can't hold addresses whose first octet is above 127.c0 a8 01 01 is 192.168.1.1.lscpu: Little Endian), /proc/net/tcp listed a loopback socket as 0100007F:170C. That's 127.0.0.1 with the bytes reversed:python3 -c "import ipaddress; p = ipaddress.ip_address('127.0.0.1').packed; print(p.hex(), p[::-1].hex())"
7f000001 0100007f
If a hex address looks like nonsense, try reading it backwards before you assume it's garbage. The IP converter handles the arithmetic once you know the byte order.
This is the part that matters for defenders. Different parsers accept different spellings of the same address.
The inet_aton(3) man page accepts four shapes: a.b.c.d, a.b.c, a.b and a bare a. In the shorter forms the last part fills the remaining bytes, and a bare number is the whole 32-bit value. Each part may be decimal, octal with a leading 0, or hex with 0x. Only the four-decimal form is "dotted-decimal."
Stricter parsers refuse all that. inet_pton(3) takes only ddd.ddd.ddd.ddd in decimal. Python's ipaddress rejects leading zeros since 3.9.5 and parses "as strict as glibc inet_pton()."
Major browsers implement the WHATWG URL Standard. Its IPv4 parser accepts hex and leading-zero octal parts and fewer than four parts. It flags a validation error, but it doesn't fail. A valid IPv4 string is still four plain decimal numbers.
Here is the gap, side by side. The columns are the input, what inet_aton makes of it, and Python's strict parser:
python3 -c "import socket, ipaddress
for s in ('2130706433', '0x7f000001', '127.1', '192.168.001.010'):
try: strict = ipaddress.ip_address(s)
except ValueError: strict = 'rejected'
print(s, socket.inet_ntoa(socket.inet_aton(s)), strict)"
2130706433 127.0.0.1 rejected
0x7f000001 127.0.0.1 rejected
127.1 127.0.0.1 rejected
192.168.001.010 192.168.1.8 rejected
Node's WHATWG URL reads all four the same way as inet_aton. Two of them:
node -e "for (const h of ['2130706433', '192.168.001.010']) console.log(h, new URL('http://' + h + '/').host)"
2130706433 127.0.0.1
192.168.001.010 192.168.1.8
Note the last line. 010 is octal 8, so a zero-padded 192.168.001.010 becomes 192.168.1.8, not .10.
Why it matters defensively. A string allowlist or denylist sees 2130706433 and 127.0.0.1 as different text. The resolver and the browser see the same host. Server-side URL fetchers are where this bites. When the filter and the connecting library disagree, the filter is checking something that never gets used. The fix:
ip in network), not with string prefixes.RFC 4632 defines the notation: an address, a /, then "a decimal value between 0 and 32 that describes the number of significant bits." Its example is 192.168.99.0/24, where the top 24 bits are ones in the mask and the low 8 are zeros. The prefix is the network part. Whatever is left over numbers the hosts.
python3 -c "import ipaddress as i
for p in (8, 16, 24, 28, 30, 31, 32):
n = i.ip_network('10.0.0.0/' + str(p))
print('/' + str(p), n.netmask, n.num_addresses, len(list(n.hosts())))"
/8 255.0.0.0 16777216 16777214
/16 255.255.0.0 65536 65534
/24 255.255.255.0 256 254
/28 255.255.255.240 16 14
/30 255.255.255.252 4 2
/31 255.255.255.254 2 2
/32 255.255.255.255 1 1
The columns are prefix, mask, total addresses and usable hosts. Total is 2 to the power of (32 minus the prefix). Usable is usually two fewer, because the all-zeros host is the network address and the all-ones host is the broadcast. RFC 1122 says host numbers can't be 0 or all ones outside its listed special cases.
Two exceptions:
The fields a subnet calculator gives you, from one run:
python3 -c "import ipaddress as i
n = i.ip_network('192.168.1.0/24')
h = list(n.hosts())
print(n.netmask, n.network_address, n.broadcast_address, n.num_addresses, len(h), h[0], h[-1])"
255.255.255.0 192.168.1.0 192.168.1.255 256 254 192.168.1.1 192.168.1.254
255.255.255.0: 24 one-bits.192.168.1.0: host bits all zero. It names the block.192.168.1.255: host bits all one.192.168.1.1 to 192.168.1.254.Paste 192.168.1.0/24 into the Subnet Calculator and you get these same fields without the Python.
IANA keeps the authoritative list in its IPv4 Special-Purpose Address Registry, restructured by RFC 6890. The ones you meet most:
| Block | Purpose | Source |
|---|---|---|
10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 | Private use | RFC 1918 |
100.64.0.0/10 | Shared address space (carrier-grade NAT) | RFC 6598 |
127.0.0.0/8 | Loopback, must not appear outside a host | RFC 1122 |
169.254.0.0/16 | Link-local (auto-configured) | RFC 3927 |
192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 | Documentation | RFC 5737 |
The registry marks every one of these as not globally reachable. 172.16.0.0/12 runs to 172.31.255.255, not just 172.16.x.x. That's a common miss in firewall rules.
Libraries draw the lines differently, so check what yours means by "private":
python3 -c "import ipaddress as i
for s in ('10.20.30.40', '172.31.0.9', '192.168.1.10', '100.64.0.1', '169.254.10.20', '127.0.0.1', '1.1.1.1'):
a = i.ip_address(s)
print(s, a.is_private, a.is_global)"
10.20.30.40 True False
172.31.0.9 True False
192.168.1.10 True False
100.64.0.1 False False
169.254.10.20 True False
127.0.0.1 True False
1.1.1.1 False True
Python counts loopback and link-local as "private," and calls 100.64.0.1 neither private nor global. If your code means "not on the public internet," test is_global, not is_private.
In a report: a private address in a public response usually means internal address disclosure or NAT. It isn't a hidden corner of the internet. Note it as information leakage, not as something to go and reach.
010 can mean 8. Strip padding before anything parses it./proc/net/tcp shows address bytes reversed. 0100007F is 127.0.0.1./24 has 256 addresses and 254 usable hosts. A /30 has 2 usable./31 is a point-to-point link.127.255.255.255, the value doesn't fit a signed 32-bit integer. Use an unsigned column.Next steps: for ports and protocols, read Linux networking basics. To turn an address back into a name, see reverse DNS and PTR records and the Reverse DNS Lookup.
They describe the same thing. /24 and 255.255.255.0 both mean the first 24 bits are the network. CIDR is the shorter way to write it and works for any length from 0 to 32.
127.0.0.1 as a 32-bit integer. On this box, getent ahostsv4 2130706433 returned 127.0.0.1, because the glibc resolver accepts the integer form.
256 addresses, 254 usable. Network and broadcast take the other two.
The ideas carry over: one big number, a prefix length, special-purpose ranges. IPv6 addresses are 128 bits and written in hex, so the forms differ. Get IPv4 solid first, then take the same habits across.
Share this article
A JWT is a signed bundle of JSON claims. Here is how to read one, why decoding is not verifying, and how to check for the classic JWT security flaws.
9 min read
Sign in to leave a comment.